├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── fr
│ │ └── s13d
│ │ └── photobackup
│ │ └── ApplicationTest.java
│ ├── debug
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── fr
│ │ └── s13d
│ │ └── photobackup
│ │ ├── Log.java
│ │ ├── PBActivity.java
│ │ ├── PBApplication.java
│ │ ├── PBBootBroadcastReceiver.java
│ │ ├── PBConstants.java
│ │ ├── PBService.java
│ │ ├── PBWifiBroadcastReceiver.java
│ │ ├── about
│ │ └── PBAboutActivity.java
│ │ ├── interfaces
│ │ ├── PBMediaSenderInterface.java
│ │ └── PBMediaStoreInterface.java
│ │ ├── journal
│ │ ├── PBJournalActivity.java
│ │ └── PBJournalAdapter.java
│ │ ├── media
│ │ ├── PBMedia.java
│ │ ├── PBMediaSender.java
│ │ ├── PBMediaStore.java
│ │ └── PBSyncMediaStoreTask.java
│ │ └── preferences
│ │ ├── PBPreferenceFragment.java
│ │ ├── PBServerListPreference.java
│ │ └── PBServerPreferenceFragment.java
│ └── res
│ ├── drawable-hdpi
│ ├── ic_backup_white_48dp.png
│ ├── ic_done_white_48dp.png
│ ├── ic_error_outline_white_48dp.png
│ └── ic_file_upload_white_24dp.png
│ ├── drawable-mdpi
│ ├── ic_backup_white_48dp.png
│ ├── ic_done_white_48dp.png
│ ├── ic_error_outline_white_48dp.png
│ └── ic_file_upload_white_24dp.png
│ ├── drawable-xhdpi
│ ├── ic_backup_white_48dp.png
│ ├── ic_done_white_48dp.png
│ ├── ic_error_outline_white_48dp.png
│ └── ic_file_upload_white_24dp.png
│ ├── drawable-xxhdpi
│ ├── ic_backup_white_48dp.png
│ ├── ic_done_white_48dp.png
│ ├── ic_error_outline_white_48dp.png
│ └── ic_file_upload_white_24dp.png
│ ├── drawable-xxxhdpi
│ ├── ic_backup_white_48dp.png
│ ├── ic_done_white_48dp.png
│ ├── ic_error_outline_white_48dp.png
│ └── ic_file_upload_white_24dp.png
│ ├── drawable
│ └── icon.png
│ ├── layout
│ ├── activity_about.xml
│ ├── activity_journal.xml
│ ├── list_row.xml
│ └── server_list_row.xml
│ ├── menu
│ └── main_menu.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-ldpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-cs
│ └── strings.xml
│ ├── values-de
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-ja
│ └── strings.xml
│ ├── values-v11
│ └── styles.xml
│ ├── values-v14
│ └── styles.xml
│ ├── values-v21
│ └── styles.xml
│ ├── values
│ ├── arrays.xml
│ ├── servers_params.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── preferences.xml
│ └── server_preferences.xml
├── build.gradle
├── codecov.yml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # Gradle files
29 | .gradle/
30 | build/
31 | /*/build/
32 |
33 | # Android Studio files
34 | .idea/
35 | project.properties
36 | /*/local.properties
37 | /*/out
38 | /*/*/build
39 | build/
40 | /*/*/production
41 | *.iml
42 | *.iws
43 | *.ipr
44 | *~
45 | *.swp
46 |
47 | # Mac oS X
48 | .DS_Store
49 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | # Turn off caching to avoid any caching problems
4 | cache: false
5 | # Use the Travis Container-Based Infrastructure
6 | sudo: false
7 | env:
8 | global:
9 | - ANDROID_API_LEVEL=25
10 | - ANDROID_BUILD_TOOLS_VERSION=25.0.0
11 | - ANDROID_ABI=armeabi-v7a
12 | - ADB_INSTALL_TIMEOUT=10 # minutes (2 minutes by default)
13 |
14 | android:
15 | components:
16 | - tools
17 | - build-tools-$ANDROID_BUILD_TOOLS_VERSION
18 | - android-$ANDROID_API_LEVEL
19 | - extra-android-m2repository
20 | # Support library
21 | - extra-android-support
22 | # Specify at least one system image
23 | - sys-img-armeabi-v7a-android-$ANDROID_API_LEVEL
24 |
25 | before_script:
26 | # Create emulator
27 | - echo no | android create avd --force -n test -t android-$ANDROID_API_LEVEL --abi $ANDROID_ABI
28 | # Start emulator
29 | - emulator -avd test -no-skin -no-audio -no-window &
30 | - adb wait-for-device &
31 | - adb shell input keyevent 82 &
32 | # inject Falcon dependency (to take screenshots)
33 | # - sed -i "s/^dependencies {/dependencies { compile 'com.jraska:falcon:1.0.1'/g" build.gradle
34 | # - sed -i "s/^repositories {/repositories { jcenter()/g" build.gradle
35 |
36 | script:
37 | # Build the app
38 | - chmod +x gradlew
39 | - ./gradlew build -PdisablePreDex
40 | # - ./gradlew connectedAndroidTest
41 |
42 | # after_script:
43 | # - adb install build/outputs/apk/client-android-debug.apk
44 | # - adb shell am start -n fr.s13d.photobackup/.PBActivity
45 | # Go to About activity
46 | # - adb shell input tap 500 1300
47 | # - bash <(curl -s https://codecov.io/bash)
48 |
49 |
50 | # gitter integration
51 | notifications:
52 | webhooks:
53 | urls:
54 | - https://webhooks.gitter.im/e/78e5e3847120190ddae2
55 | on_success: change # options: [always|never|change] default: always
56 | on_failure: always # options: [always|never|change] default: always
57 | on_start: never # options: [always|never|change] default: always
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | # 0.9.0
4 | * [4a9e39c](https://github.com/PhotoBackup/client-android/commit/4a9e39c) Switch from loopj to OkHttp (to handle HTTPS SNI #23 and redirection #15) ;
5 | * [67eeb00](https://github.com/PhotoBackup/client-android/commit/67eeb00) Async update of Upload Log ui after manual upload ;
6 | * [71e7dee](https://github.com/PhotoBackup/client-android/commit/71e7dee) Saving the error message per photo and display it under the file name ;
7 | * [c5cfe04](https://github.com/PhotoBackup/client-android/commit/c5cfe04) New German translation thanks to @zealot128 ;
8 | * [0892104](https://github.com/PhotoBackup/client-android/commit/0892104) New Czech translation thanks to @svetlemodry ;
9 | * [ca629b3](https://github.com/PhotoBackup/client-android/commit/ca629b3) handling 409 status code properly #45 ;
10 | * [1108063](https://github.com/PhotoBackup/client-android/commit/1108063) remove 'Stop the service' action in the notification #41 ;
11 | * some Sonar-induced fixes ([c8ccce8](https://github.com/PhotoBackup/client-android/commit/c8ccce8), [af13b2c](https://github.com/PhotoBackup/client-android/commit/af13b2c), [f0f8508](https://github.com/PhotoBackup/client-android/commit/f0f8508) and [8ce3611](https://github.com/PhotoBackup/client-android/commit/8ce3611)) ;
12 | * finally, an operational Travis build: https://travis-ci.org/PhotoBackup/client-android ;
13 | * for developers, a gitter channel to discuss: https://gitter.im/PhotoBackup
14 |
15 |
16 | # 0.8.0
17 | * [3c4f128](https://github.com/PhotoBackup/client-android/commit/3c4f128) Czech translation
18 | * [47a0c62](https://github.com/PhotoBackup/client-android/commit/47a0c62) Add multiple servers capability basic architecture
19 |
20 | ## 0.7.2
21 | * [dae700c](https://github.com/PhotoBackup/client-android/commit/dae700c) Fix 0.6 => 0.7 migration by setting it before addPreferencesFromResources
22 |
23 | ## 0.7.1
24 | * [29ac3dc](https://github.com/PhotoBackup/client-android/commit/29ac3dc) Remove Handler messages for recycled views
25 | * [2080fc0](https://github.com/PhotoBackup/client-android/commit/2080fc0) Fix 'Only wifi' preference type bug in v0.7.0 by adding a migration
26 |
27 | ## 0.7.0
28 | * [c5f5cab](https://github.com/PhotoBackup/client-android/commit/c5f5cab) Add an option to upload only recently taken pictures.
29 | * [4321481](https://github.com/PhotoBackup/client-android/commit/4321481) Add filter to journal activity
30 | * [7cf98d5](https://github.com/PhotoBackup/client-android/commit/7cf98d5) Set delay to new photos up to 10 minutes
31 | * [a07628f](https://github.com/PhotoBackup/client-android/commit/a07628f) Fix erroneous number of pictures in journal entry
32 |
33 | ## 0.6.5
34 | * [b90deb4](https://github.com/PhotoBackup/client-android/commit/b90deb4) Support Android M runtime permissions
35 | * [b7bf565](https://github.com/PhotoBackup/client-android/commit/b7bf565) Update AAHC to version 1.4.9
36 | * [84e540a](https://github.com/PhotoBackup/client-android/commit/84e540a) Move permission callback from fragment to activity
37 |
38 | ## 0.6.4
39 | * [47e5ce3](https://github.com/PhotoBackup/client-android/commit/47e5ce3) Add notification action to stop the service from it.
40 | * [4da4364](https://github.com/PhotoBackup/client-android/commit/4da4364) Update for Android M
41 |
42 | ## 0.6.3
43 | * [2dd370a](https://github.com/PhotoBackup/client-android/commit/2dd370a) Fix bug: boot receiver not reading user-defined prefernce
44 |
45 | ## 0.6.2
46 | * [eada898](https://github.com/PhotoBackup/client-android/commit/eada898) Fix bug #3 by removing slashes at the end of URLs
47 | * [8b37bbb](https://github.com/PhotoBackup/client-android/commit/8b37bbb) Fix #2: set network choosing as a modal single choice dialog
48 | * [a689da7](https://github.com/PhotoBackup/client-android/commit/a689da7) Update error icon
49 |
50 | ## 0.6.1
51 | * [5e368d0](https://github.com/PhotoBackup/client-android/commit/5e368d0) Fix password hash when changing password
52 | * [b5ef6d4](https://github.com/PhotoBackup/client-android/commit/b5ef6d4) Add screenshot image
53 | * [c0aeccb](https://github.com/PhotoBackup/client-android/commit/c0aeccb) Remove unused text
54 | * [10f0faf](https://github.com/PhotoBackup/client-android/commit/10f0faf) Add README.md
55 |
56 | ## 0.6
57 | * [163f604](https://github.com/PhotoBackup/client-android/commit/163f604) Add an 'About' activity to explain quickly the purpose of the app and
58 |
59 | ## 0.5
60 | * [7747624](https://github.com/PhotoBackup/client-android/commit/7747624) Add file size in the request parameter
61 |
62 | ## 0.4
63 | * [6e1ca37](https://github.com/PhotoBackup/client-android/commit/6e1ca37) Initial commit
64 |
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/PhotoBackup/client-android)
2 | # PhotoBackup Android client
3 |
4 | The PhotoBackup Android application is a simple service always running
5 | silently in background and sending photos to the associated server
6 | as soon as you take them. An upload journal allows you to see the status
7 | of each photo in your device. It does not backup videos.
8 |
9 | It is available on the
10 | [Google Play](https://play.google.com/store/apps/details?id=fr.s13d.photobackup)
11 | and on [F-Droid](https://f-droid.org/app/fr.s13d.photobackup).
12 |
13 | It does not use services like Google Drive/Dropbox/SkyDrive/... **on purpose**,
14 | as you don't know what they do with your photos. With PhotoBackup, only you should do.
15 |
16 | Join our dedicated online chat at [](https://gitter.im/PhotoBackup/client-android).
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | ext.supportLibVersion = '25.0.0'
4 |
5 |
6 | android {
7 | compileSdkVersion 25
8 | buildToolsVersion "24.0.3"
9 | dataBinding.enabled = true
10 |
11 | defaultConfig {
12 | applicationId "fr.s13d.photobackup"
13 | minSdkVersion 16
14 | targetSdkVersion 25
15 | versionCode 33
16 | versionName "0.11.0"
17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
18 | }
19 | buildTypes {
20 | debug {
21 | debuggable true
22 | minifyEnabled false
23 | applicationIdSuffix ".debug"
24 | versionNameSuffix '-dev'
25 | }
26 | release {
27 | debuggable false
28 | minifyEnabled true
29 | shrinkResources true
30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 | lintOptions {
34 | abortOnError false
35 | }
36 |
37 | // to force Gradle version conflicts resolving
38 | configurations.all {
39 | resolutionStrategy.force 'com.google.code.findbugs:jsr305:2.0.1'
40 | }
41 | }
42 |
43 | dependencies {
44 | // compile
45 | compile fileTree(dir: 'libs', include: ['*.jar'])
46 | compile "com.android.support:appcompat-v7:${supportLibVersion}"
47 | compile 'com.squareup.okhttp3:okhttp:3.2.0'
48 | compile('de.psdev.licensesdialog:licensesdialog:1.8.1')
49 |
50 | // jUnit
51 | testCompile 'junit:junit:4.12'
52 |
53 | // androidTestCompile
54 | androidTestCompile 'com.android.support.test:runner:0.5'
55 | androidTestCompile 'com.android.support.test:rules:0.5'
56 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
57 | androidTestCompile "com.android.support:appcompat-v7:${supportLibVersion}"
58 | }
59 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/stephane/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # do not print notes
20 | -dontnote
21 |
22 | # keep attributes
23 | -keepattributes EnclosingMethod
24 |
25 | # keep class from included classes
26 | -dontwarn java.nio.file.**
27 | -keep class java.nio.file.** { *; }
28 |
29 | -dontwarn org.codehaus.mojo.**
30 | -keep class org.codehaus.mojo.** { *; }
31 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/fr/s13d/photobackup/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup;
20 |
21 | import android.support.test.InstrumentationRegistry;
22 | import android.support.test.filters.LargeTest;
23 | import android.support.test.rule.ActivityTestRule;
24 | import android.support.test.runner.AndroidJUnit4;
25 |
26 | import org.junit.Rule;
27 | import org.junit.Test;
28 | import org.junit.runner.RunWith;
29 |
30 | import static android.support.test.espresso.Espresso.onView;
31 | import static android.support.test.espresso.action.ViewActions.click;
32 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
33 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
34 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
35 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
36 |
37 |
38 | @RunWith(AndroidJUnit4.class)
39 | @LargeTest
40 | public class ApplicationTest {
41 |
42 | @Rule
43 | public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(PBActivity.class);
44 |
45 | @Test
46 | public void startMainActivityShouldStartWithBasicContent() {
47 | // this is the PhotoBackup activity, it contains its name
48 | onView(withText(R.string.app_name)).check(matches(isDisplayed()));
49 |
50 | // Service is not running at start
51 | onView(withText(R.string.service_title)).check(matches(isDisplayed()));
52 | onView(withText(R.string.service_state_not_running)).check(matches(isDisplayed()));
53 |
54 | onView(withText(R.string.server_pref_title)).check(matches(isDisplayed()));
55 | }
56 |
57 |
58 | @Test
59 | public void clickOnAboutPreferenceShouldOpenAboutActivity() {
60 | onView(withText(R.string.app_name)).check(matches(isDisplayed()));
61 |
62 | // Service is not running at start
63 | onView(withText(R.string.service_state_not_running)).check(matches(isDisplayed()));
64 |
65 | onView(withText(R.string.about_title)).check(matches(isDisplayed()));
66 |
67 | // open About activity
68 | onView(withText(R.string.about_title)).perform(click());
69 |
70 | // check that About activity is opened
71 | String aboutText = InstrumentationRegistry.getTargetContext().getString(R.string.about_text);
72 | onView(withId(R.id.aboutTextTextView)).check(matches(withText(aboutText)));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PB Debug
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/Log.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup;
20 |
21 |
22 | /**
23 | * Own logger class to avoid leaking logs in release mode.
24 | */
25 | public final class Log {
26 |
27 | private Log() throws InstantiationException {
28 | throw new InstantiationException("This class is not meant to be instantiated!");
29 | }
30 |
31 |
32 | /**
33 | * Log a debug message
34 | * @param tag tag of the logged message
35 | * @param message message logged
36 | */
37 | public static void d(final String tag, final String message) {
38 | if (BuildConfig.DEBUG) {
39 | android.util.Log.d(tag, message);
40 | }
41 | }
42 |
43 |
44 | /**
45 | * Log a debug exception
46 | * @param tag tag of the logged exception
47 | * @param e exception logged
48 | */
49 | public static void d(final String tag, final Exception e) {
50 | if (BuildConfig.DEBUG) {
51 | android.util.Log.d(tag, e.toString());
52 | }
53 | }
54 |
55 |
56 | /**
57 | * Log an error message
58 | * @param tag tag of the logged message
59 | * @param message message logged
60 | */
61 | public static void e(final String tag, final String message) {
62 | if (BuildConfig.DEBUG) {
63 | android.util.Log.e(tag, message);
64 | }
65 | }
66 |
67 |
68 | /**
69 | * Log an error exception
70 | * @param tag tag of the logged exception
71 | * @param e exception logged
72 | */
73 | public static void e(final String tag, final Exception e) {
74 | if (BuildConfig.DEBUG) {
75 | android.util.Log.e(tag, e.toString());
76 | }
77 | }
78 |
79 |
80 | /**
81 | * Log an info message
82 | * @param tag tag of the logged message
83 | * @param message message logged
84 | */
85 | public static void i(final String tag, final String message) {
86 | if (BuildConfig.DEBUG) {
87 | android.util.Log.i(tag, message);
88 | }
89 | }
90 |
91 |
92 | /**
93 | * Log an info exception
94 | * @param tag tag of the logged exception
95 | * @param e exception logged
96 | */
97 | public static void i(final String tag, final Exception e) {
98 | if (BuildConfig.DEBUG) {
99 | android.util.Log.i(tag, e.toString());
100 | }
101 | }
102 |
103 |
104 | /**
105 | * Log a verbose message
106 | * @param tag tag of the logged message
107 | * @param message message logged
108 | */
109 | public static void v(final String tag, final String message) {
110 | if (BuildConfig.DEBUG) {
111 | android.util.Log.v(tag, message);
112 | }
113 | }
114 |
115 |
116 | /**
117 | * Log a verbose exception
118 | * @param tag tag of the logged exception
119 | * @param e exception logged
120 | */
121 | public static void v(final String tag, final Exception e) {
122 | if (BuildConfig.DEBUG) {
123 | android.util.Log.v(tag, e.toString());
124 | }
125 | }
126 |
127 |
128 | /**
129 | * Log a warning message
130 | * @param tag tag of the logged message
131 | * @param message message logged
132 | */
133 | public static void w(final String tag, final String message) {
134 | if (BuildConfig.DEBUG) {
135 | android.util.Log.w(tag, message);
136 | }
137 | }
138 |
139 |
140 | /**
141 | * Log a warning exception
142 | * @param tag tag of the logged exception
143 | * @param e exception logged
144 | */
145 | public static void w(final String tag, final Exception e) {
146 | if (BuildConfig.DEBUG) {
147 | android.util.Log.w(tag, e.toString());
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup;
20 |
21 | import android.app.Activity;
22 | import android.app.NotificationManager;
23 | import android.content.Context;
24 | import android.content.pm.PackageManager;
25 | import android.os.Bundle;
26 | import android.support.annotation.NonNull;
27 | import android.view.MenuItem;
28 | import android.widget.Toast;
29 |
30 | import fr.s13d.photobackup.preferences.PBPreferenceFragment;
31 |
32 | public class PBActivity extends Activity {
33 |
34 | private static final PBPreferenceFragment preferenceFragment = new PBPreferenceFragment();
35 |
36 |
37 | //////////////
38 | // Override //
39 | //////////////
40 | @Override
41 | protected void onCreate(final Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | getFragmentManager().beginTransaction().replace(android.R.id.content, preferenceFragment).commit();
44 | setActionBar();
45 | }
46 |
47 |
48 | @Override
49 | protected void onStart() {
50 | super.onStart();
51 | final NotificationManager nm = (NotificationManager) PBApplication.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
52 | nm.cancelAll();
53 | }
54 |
55 |
56 | @Override
57 | public void onRequestPermissionsResult(int requestCode,
58 | @NonNull String[] permissions,
59 | @NonNull int[] grantResults) {
60 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
61 |
62 | if (requestCode == PBPreferenceFragment.PERMISSION_READ_EXTERNAL_STORAGE) {
63 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
64 | Log.i("PBPreferenceFragment", "READ_EXTERNAL_STORAGE permission granted.");
65 | preferenceFragment.didGrantPermission(); // continue to next step
66 | } else {
67 | Log.i("PBPreferenceFragment", "READ_EXTERNAL_STORAGE was NOT granted.");
68 | Toast.makeText(this, R.string.toast_permission_not_granted, Toast.LENGTH_LONG).show();
69 | }
70 | }
71 | }
72 |
73 |
74 | @Override
75 | public boolean onOptionsItemSelected(MenuItem item) {
76 | if (item.getItemId() == android.R.id.home) {
77 | getFragmentManager().popBackStackImmediate();
78 | setActionBar();
79 | }
80 | return super.onOptionsItemSelected(item);
81 | }
82 |
83 |
84 | /////////////
85 | // methods //
86 | /////////////
87 | public void setActionBar() {
88 | // title and back button of the action bar
89 | setTitle(R.string.app_name);
90 | if (getActionBar() != null) {
91 | getActionBar().setDisplayHomeAsUpEnabled(false);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBApplication.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup;
20 |
21 | import android.app.Application;
22 | import android.preference.PreferenceManager;
23 |
24 | import java.util.Map;
25 |
26 | import fr.s13d.photobackup.media.PBMediaStore;
27 |
28 |
29 | /**
30 | * Application of the app
31 | */
32 | public class PBApplication extends Application {
33 |
34 | private static final String LOG_TAG = "PBApplication";
35 | private static PBApplication app;
36 | private static PBMediaStore mediaStore;
37 | private static final Object lock = new Object();
38 |
39 |
40 | ///////////////
41 | // Constants //
42 | ///////////////
43 | public static final String PB_USER_AGENT = "PhotoBackup Android Client v" + BuildConfig.VERSION_NAME;
44 | public static final String PB_MEDIAS_SHARED_PREFS = "PB_MEDIAS_SHARED_PREFS";
45 |
46 |
47 | ////////////////
48 | // Life-cycle //
49 | ////////////////
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 | Log.d(LOG_TAG, "Initializing app");
54 | setApp(this);
55 |
56 | if (BuildConfig.DEBUG) {
57 | Log.d(LOG_TAG, "Default SharedPreferences:");
58 | final Map allPrefs = PreferenceManager.getDefaultSharedPreferences(this).getAll();
59 | for (final Map.Entry entry : allPrefs.entrySet()) {
60 | Log.d(LOG_TAG, entry.getKey() + "<" + entry.getValue().getClass().getSimpleName() + "> = "
61 | + entry.getValue().toString());
62 | }
63 | }
64 | }
65 |
66 |
67 | @Override
68 | public void onLowMemory() {
69 | super.onLowMemory();
70 | trimMemory();
71 | }
72 |
73 |
74 | @Override
75 | public void onTrimMemory(int level) {
76 | super.onTrimMemory(level);
77 | trimMemory();
78 | }
79 |
80 |
81 | private void trimMemory() {
82 | Log.d(LOG_TAG, "trimMemory");
83 | if (mediaStore != null) {
84 | mediaStore.close();
85 | nullifyMediaStore();
86 | }
87 | }
88 |
89 |
90 | /////////////////////
91 | // Getters/setters //
92 | /////////////////////
93 | public static PBApplication getApp() {
94 | return app;
95 | }
96 |
97 |
98 | private static void setApp(PBApplication application) {
99 | app = application;
100 | }
101 |
102 |
103 | public static PBMediaStore getMediaStore() {
104 | synchronized (lock) {
105 | if (mediaStore == null) {
106 | mediaStore = new PBMediaStore();
107 | mediaStore.sync();
108 | }
109 | }
110 | return mediaStore;
111 | }
112 |
113 | /**
114 | * Nullify the media store when destroying it.
115 | */
116 | public static void nullifyMediaStore() {
117 | synchronized (lock) {
118 | mediaStore = null;
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBBootBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package fr.s13d.photobackup;
21 |
22 | import android.content.BroadcastReceiver;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.SharedPreferences;
26 | import android.preference.PreferenceManager;
27 |
28 |
29 | /**
30 | * Receives boot broadcast intents and start PB service if required.
31 | */
32 | public class PBBootBroadcastReceiver extends BroadcastReceiver {
33 |
34 | @Override
35 | public void onReceive(final Context context, final Intent intent) {
36 |
37 | if (intent != null && "android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {
38 | final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
39 | final boolean shouldRun = preferences.getBoolean(PBConstants.PREF_SERVICE_RUNNING, false);
40 | if (shouldRun) {
41 | final Intent startServiceIntent = new Intent(context, PBService.class);
42 | context.startService(startServiceIntent);
43 | }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBConstants.java:
--------------------------------------------------------------------------------
1 | package fr.s13d.photobackup;
2 |
3 |
4 | /**
5 | * Delivers all public constants to the application.
6 | */
7 | public class PBConstants {
8 |
9 | // should correspond to what is in preferences.xml
10 | public static final String PREF_SERVICE_RUNNING = "PREF_SERVICE_RUNNING";
11 | public static final String PREF_SERVER = "PREF_SERVER";
12 | public static final String PREF_WIFI_ONLY = "PREF_WIFI_ONLY";
13 | public static final String PREF_MEDIAS_TO_BACKUP = "PREF_MEDIAS_TO_BACKUP";
14 | public static final String PREF_RECENT_UPLOAD_ONLY = "PREF_RECENT_UPLOAD_ONLY";
15 | public static final String PREF_PICTURE_FOLDER_LIST = "PREF_PICTURE_FOLDER_LIST";
16 | public static final String PREF_MEDIA_BACKUP_VIDEO = "PREF_MEDIA_BACKUP_VIDEO";
17 | public static final String PREF_UPLOAD_JOURNAL = "PREF_UPLOAD_JOURNAL";
18 | public static final String PREF_ABOUT = "PREF_ABOUT";
19 |
20 | // origin to permission asking, to be able to reroute
21 | public static final int PERM_ORIGIN_SERVICE = 0;
22 | public static final int PERM_ORIGIN_MEDIAS = 1;
23 |
24 |
25 | // Prevent from constructing objects of this class
26 | private PBConstants() { throw new AssertionError(); }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBService.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup;
20 |
21 | import android.app.Service;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.database.ContentObserver;
25 | import android.net.Uri;
26 | import android.os.IBinder;
27 | import android.preference.PreferenceManager;
28 | import android.provider.MediaStore;
29 |
30 | import fr.s13d.photobackup.interfaces.PBMediaSenderInterface;
31 | import fr.s13d.photobackup.interfaces.PBMediaStoreInterface;
32 | import fr.s13d.photobackup.media.PBMedia;
33 | import fr.s13d.photobackup.media.PBMediaSender;
34 |
35 |
36 | /**
37 | * Main background service doing the whole job of listening
38 | * to new media being saved and initiating media uploads.
39 | */
40 | public class PBService extends Service implements PBMediaStoreInterface, PBMediaSenderInterface {
41 |
42 | private static final String LOG_TAG = "PBService";
43 | private static MediaContentObserver imagesContentObserver;
44 | private static MediaContentObserver videosContentObserver;
45 | private PBMediaSender mediaSender;
46 | private Binder binder;
47 |
48 |
49 | //////////////
50 | // Override //
51 | //////////////
52 | @Override
53 | public void onCreate() {
54 | super.onCreate();
55 | binder = new Binder();
56 | setImagesContentObserver(new MediaContentObserver());
57 | this.getApplicationContext().getContentResolver().registerContentObserver(
58 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, imagesContentObserver);
59 | setVideosContentObserver(new MediaContentObserver());
60 | this.getApplicationContext().getContentResolver().registerContentObserver(
61 | MediaStore.Video.Media.EXTERNAL_CONTENT_URI, false, videosContentObserver);
62 | PBApplication.getMediaStore().addInterface(this);
63 |
64 | Log.i(LOG_TAG, "PhotoBackup service is created");
65 | }
66 |
67 |
68 | @Override
69 | public void onDestroy() {
70 | super.onDestroy();
71 | this.getApplicationContext().getContentResolver().unregisterContentObserver(imagesContentObserver);
72 | this.getApplicationContext().getContentResolver().unregisterContentObserver(videosContentObserver);
73 | setImagesContentObserver(null);
74 | setVideosContentObserver(null);
75 | PBApplication.getMediaStore().removeInterface(this);
76 | PBApplication.nullifyMediaStore();
77 |
78 | Log.i(LOG_TAG, "PhotoBackup service has stopped");
79 | }
80 |
81 |
82 | @Override
83 | public int onStartCommand(final Intent intent, final int flags, final int startId) {
84 | super.onStartCommand(intent, flags, startId);
85 | Log.i(LOG_TAG, "PhotoBackup service has started");
86 | return START_STICKY;
87 | }
88 |
89 |
90 | @Override
91 | public IBinder onBind(Intent intent) {
92 | return binder;
93 | }
94 |
95 |
96 | /**
97 | * Binder class
98 | */
99 | public class Binder extends android.os.Binder {
100 | public PBService getService() {
101 | return PBService.this;
102 | }
103 | }
104 |
105 |
106 | /////////////
107 | // Methods //
108 | /////////////
109 | /**
110 | * Sends next media in store.
111 | */
112 | public void sendNextMedia() {
113 | final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(PBApplication.getApp());
114 | final boolean isRunning = preferences.getBoolean(PBConstants.PREF_SERVICE_RUNNING, false);
115 | if (isRunning) {
116 | for (PBMedia media : PBApplication.getMediaStore().getMediaList()) {
117 | if (media.getState() != PBMedia.PBMediaState.SYNCED) {
118 | sendMedia(media, false);
119 | break;
120 | }
121 | }
122 | }
123 | }
124 |
125 |
126 | /**
127 | * Initiates a media media upload.
128 | * @param media media to be sent
129 | * @param manual indicates if upload is manually or automatically (through sendNextMedia()) initiated.
130 | */
131 | public void sendMedia(PBMedia media, boolean manual) {
132 | getMediaSender().send(media, manual);
133 | }
134 |
135 |
136 | ////////////////////////////////////
137 | // PBMediaStoreListener callbacks //
138 | ////////////////////////////////////
139 | /**
140 | * Sends next media on media store synchronisation.
141 | */
142 | public void onSyncMediaStoreTaskPostExecute() {
143 | sendNextMedia();
144 | }
145 |
146 |
147 | //////////////////////////////////////
148 | // PBMediaSenderInterface callbacks //
149 | //////////////////////////////////////
150 | /**
151 | * Does nothing on receiving a message.
152 | * @param message received message
153 | */
154 | public void onMessage(final String message) {
155 | // Do nothing
156 | }
157 |
158 |
159 | /**
160 | * Send next media on sending with success.
161 | */
162 | public void onSendSuccess() {
163 | sendNextMedia();
164 | }
165 |
166 |
167 | /**
168 | * Does nothing on sending with failure.
169 | */
170 | public void onSendFailure() {
171 | // Do nothing
172 | }
173 |
174 |
175 | /**
176 | * Does nothing on test success.
177 | */
178 | public void onTestSuccess() {
179 | // Do nothing
180 | }
181 |
182 |
183 | /**
184 | * Does nothing on test failure.
185 | */
186 | public void onTestFailure() {
187 | // Do nothing
188 | }
189 |
190 |
191 | /////////////////////////////////////////////////////////////
192 | // ContentObserver to react on the creation of a new media //
193 | /////////////////////////////////////////////////////////////
194 | private class MediaContentObserver extends ContentObserver {
195 |
196 | private MediaContentObserver() {
197 | super(null);
198 | }
199 |
200 | @Override
201 | public void onChange(boolean selfChange, Uri uri) {
202 | super.onChange(selfChange);
203 | Log.i(LOG_TAG, "MediaContentObserver:onChange()");
204 |
205 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(PBService.this);
206 | final boolean backupVideos = sp.getBoolean(PBConstants.PREF_MEDIA_BACKUP_VIDEO, false);
207 | if ("content://media/external/images/media".equals(uri.toString()) ||
208 | (backupVideos && "content://media/external/video/media".equals(uri.toString()))) {
209 |
210 | try {
211 | final PBMedia media = PBApplication.getMediaStore().createMediaForLatestInStore(backupVideos);
212 | if (media != null) {
213 | media.setState(PBMedia.PBMediaState.WAITING);
214 | getMediaSender().send(media, false);
215 | }
216 | }
217 | catch (Exception e) {
218 | Log.e(LOG_TAG, "Upload failed :-(");
219 | Log.w(LOG_TAG, e);
220 | }
221 | }
222 | }
223 | }
224 |
225 |
226 | /////////////////////
227 | // getters/setters //
228 | /////////////////////
229 | private PBMediaSender getMediaSender() {
230 | if (mediaSender == null) {
231 | mediaSender = new PBMediaSender();
232 | mediaSender.addInterface(this);
233 | }
234 | return mediaSender;
235 | }
236 |
237 |
238 | private static void setVideosContentObserver(MediaContentObserver contentObserver) {
239 | videosContentObserver = contentObserver;
240 | }
241 |
242 |
243 | private static void setImagesContentObserver(MediaContentObserver contentObserver) {
244 | imagesContentObserver = contentObserver;
245 | }
246 |
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/PBWifiBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package fr.s13d.photobackup;
21 |
22 | import android.content.BroadcastReceiver;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.SharedPreferences;
26 | import android.net.wifi.WifiManager;
27 | import android.os.IBinder;
28 | import android.preference.PreferenceManager;
29 |
30 | import java.util.List;
31 |
32 | import fr.s13d.photobackup.media.PBMedia;
33 |
34 |
35 | /**
36 | * Receives wifi broadcast intents and starts media upload if required.
37 | */
38 | public class PBWifiBroadcastReceiver extends BroadcastReceiver {
39 | private static final String LOG_TAG = "PBWifiBroadcastReceiver";
40 | private static long lastFiredOn = 0;
41 |
42 |
43 | @Override
44 | public void onReceive(final Context context, final Intent intent) {
45 | // Receiver should only live a short time, but the onReceive is called multiple times
46 | // Only accept reception every 10 minutes
47 | final long now = System.currentTimeMillis() / 1000;
48 | if (now - getLastFiredOn() < 600) {
49 | return;
50 | }
51 |
52 | final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
53 | final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
54 | final String wifiOnlyString = preferences.getString(PBConstants.PREF_WIFI_ONLY, context.getResources().getString(R.string.only_wifi_default));
55 | final Boolean wifiOnly = wifiOnlyString.equals(context.getResources().getString(R.string.only_wifi));
56 |
57 | Log.i(LOG_TAG, "New intent: action=" + intent.getAction() + ", type=" + intent.getType());
58 | if (wifiManager.isWifiEnabled() && wifiOnly) {
59 | handleWifi(context, now);
60 | }
61 | }
62 |
63 |
64 | private void handleWifi(final Context context, final long now) {
65 | Log.i(LOG_TAG, "Wifi comes back, checking Service");
66 | final IBinder binder = peekService(context, new Intent(context, PBService.class));
67 | if (binder == null) {
68 | return;
69 | }
70 | final PBService service = ((PBService.Binder) binder).getService();
71 | if (service == null) {
72 | return;
73 | }
74 | setLastFiredOn(now);
75 |
76 | final List medias = PBApplication.getMediaStore().getMediaList();
77 | Log.i(LOG_TAG, "media count = " + medias.size());
78 |
79 | for (final PBMedia media : medias) {
80 | if (media.getAge() < 3600 * 24 * 7 && media.getState() != PBMedia.PBMediaState.SYNCED) {
81 | Log.i(LOG_TAG, "Notify to send " + media.getPath());
82 | service.sendMedia(media, true);
83 | }
84 | }
85 | }
86 |
87 |
88 | /////////////////////
89 | // getters/setters //
90 | /////////////////////
91 | private static long getLastFiredOn() {
92 | return lastFiredOn;
93 | }
94 |
95 | private static void setLastFiredOn(long lastFiredOn) {
96 | PBWifiBroadcastReceiver.lastFiredOn = lastFiredOn;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/about/PBAboutActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.about;
20 |
21 | import android.app.Activity;
22 | import android.databinding.DataBindingUtil;
23 | import android.os.Bundle;
24 | import android.view.View;
25 |
26 | import de.psdev.licensesdialog.LicensesDialog;
27 | import de.psdev.licensesdialog.licenses.ApacheSoftwareLicense20;
28 | import de.psdev.licensesdialog.model.Notice;
29 | import de.psdev.licensesdialog.model.Notices;
30 | import fr.s13d.photobackup.BuildConfig;
31 | import fr.s13d.photobackup.R;
32 | import fr.s13d.photobackup.databinding.ActivityAboutBinding;
33 |
34 |
35 | public class PBAboutActivity extends Activity {
36 |
37 | @Override
38 | protected void onCreate(final Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 |
41 | // Set up the UI (with binding)
42 | ActivityAboutBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_about);
43 | String versionString = BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ')';
44 | binding.versionTextView.setText(getString(R.string.app_version, versionString));
45 | }
46 |
47 |
48 | public void clickOnOpenSourceLicenses(final View view) throws Exception {
49 | final Notices notices = new Notices();
50 | notices.addNotice(new Notice(
51 | "Android v7 Support Libraries",
52 | "https://developer.android.com/topic/libraries/support-library/features.html#v7",
53 | "Copyright (C) 2012 The Android Open Source Project",
54 | new ApacheSoftwareLicense20()));
55 | notices.addNotice(new Notice(
56 | "LicensesDialog",
57 | "http://psdev.de",
58 | "Copyright 2013 Philip Schiffer ",
59 | new ApacheSoftwareLicense20()));
60 | notices.addNotice(new Notice(
61 | "OkHttp",
62 | "http://square.github.io/okhttp/",
63 | "Copyright 2016 Square, Inc.",
64 | new ApacheSoftwareLicense20()));
65 |
66 | new LicensesDialog.Builder(this)
67 | .setNotices(notices)
68 | .build()
69 | .show();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/interfaces/PBMediaSenderInterface.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.interfaces;
20 |
21 |
22 | public interface PBMediaSenderInterface {
23 |
24 | void onMessage(final String message);
25 |
26 | void onSendSuccess();
27 | void onSendFailure();
28 |
29 | void onTestSuccess();
30 | void onTestFailure();
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/interfaces/PBMediaStoreInterface.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package fr.s13d.photobackup.interfaces;
21 |
22 |
23 | public interface PBMediaStoreInterface {
24 | void onSyncMediaStoreTaskPostExecute();
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/journal/PBJournalActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.journal;
20 |
21 | import android.app.Activity;
22 | import android.content.Context;
23 | import android.content.SharedPreferences;
24 | import android.graphics.Bitmap;
25 | import android.os.Handler;
26 | import android.os.HandlerThread;
27 | import android.os.Message;
28 | import android.preference.PreferenceManager;
29 | import android.provider.MediaStore.Video;
30 | import android.provider.MediaStore.Images;
31 | import android.support.annotation.NonNull;
32 | import android.view.LayoutInflater;
33 | import android.view.View;
34 | import android.view.ViewGroup;
35 | import android.widget.ArrayAdapter;
36 | import android.widget.Filter;
37 | import android.widget.Filterable;
38 | import android.widget.ImageView;
39 | import android.widget.TextView;
40 |
41 | import java.io.File;
42 | import java.lang.ref.WeakReference;
43 | import java.util.ArrayList;
44 | import java.util.List;
45 |
46 | import fr.s13d.photobackup.Log;
47 | import fr.s13d.photobackup.R;
48 | import fr.s13d.photobackup.media.PBMedia;
49 |
50 |
51 | class PBJournalAdapter extends ArrayAdapter implements Filterable, Handler.Callback {
52 | private static final String LOG_TAG = "PBJournalAdapter";
53 | private static LayoutInflater inflater;
54 | private final Context context;
55 | private final SharedPreferences preferences;
56 | private final WeakReference handlerWeakReference;
57 | private Filter filter;
58 | private final List medias;
59 | private List filteredMedias;
60 |
61 |
62 | PBJournalAdapter(final Activity activity, final List medias) {
63 | super(activity, 0, medias);
64 |
65 | this.context = activity;
66 | this.preferences = PreferenceManager.getDefaultSharedPreferences(activity);
67 | this.medias = medias;
68 | this.filteredMedias = new ArrayList<>(medias.size());
69 |
70 | inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
71 |
72 | // Create and start a new thread for the handler
73 | final HandlerThread handlerThread = new HandlerThread("BackgroundThread");
74 | handlerThread.start();
75 | handlerWeakReference = new WeakReference<>(new Handler(handlerThread.getLooper(), this));
76 | }
77 |
78 |
79 | void close() {
80 | if (handlerWeakReference != null) {
81 | final Handler handler = handlerWeakReference.get();
82 | if (handler != null) {
83 | handler.getLooper().quit();
84 | }
85 | }
86 | }
87 |
88 |
89 | //////////////////////
90 | // Handler.Callback //
91 | //////////////////////
92 | @Override
93 | public boolean handleMessage(Message message) {
94 | // done on async thread
95 | final View view = (View) message.obj;
96 | final ImageView thumbImageView = (ImageView) view.findViewById(R.id.thumbnail);
97 | Bitmap bitmap = Images.Thumbnails.getThumbnail(context.getContentResolver(),
98 | message.what, Images.Thumbnails.MICRO_KIND, null);
99 | if (bitmap == null) {
100 | bitmap = Video.Thumbnails.getThumbnail(context.getContentResolver(),
101 | message.what, Video.Thumbnails.MICRO_KIND, null);
102 | }
103 | final Bitmap fBitmap = bitmap;
104 |
105 | // back on UI thread to set the bitmap to the view
106 | new Handler(context.getMainLooper()).post(new Runnable() {
107 | @Override
108 | public void run() {
109 | thumbImageView.setImageBitmap(fBitmap);
110 | }
111 | });
112 |
113 | return true;
114 | }
115 |
116 |
117 | /////////////
118 | // Adapter //
119 | /////////////
120 | @NonNull
121 | @Override
122 | public View getView(int position, View adapterView, @NonNull ViewGroup parent) {
123 | // create the view if not available
124 | final View view = (adapterView == null) ? inflater.inflate(R.layout.list_row, parent, false) : adapterView;
125 |
126 | final PBMedia media = filteredMedias.get(position);
127 | if (media == null || media.getId() == -1) {
128 | return view;
129 | }
130 |
131 | // create thumbnail
132 | if (handlerWeakReference != null) {
133 | final Handler handler = handlerWeakReference.get();
134 | if (handler != null) {
135 | handler.obtainMessage(media.getId(), view).sendToTarget();
136 | }
137 | }
138 |
139 | // filename
140 | final TextView textView = (TextView) view.findViewById(R.id.filename);
141 | if (media.getPath() != null) {
142 | final File file = new File(media.getPath());
143 | textView.setText(file.getName());
144 | } else {
145 | textView.setText(R.string.journal_error);
146 | }
147 |
148 | // indicator
149 | setIndicator(view, media);
150 |
151 | return view;
152 | }
153 |
154 |
155 | private void setIndicator(final View view, final PBMedia media) {
156 | final TextView errorTextView = (TextView) view.findViewById(R.id.errorHint);
157 | final ImageView imageView = (ImageView) view.findViewById(R.id.state);
158 | if (media.getState() == PBMedia.PBMediaState.WAITING) {
159 | errorTextView.setVisibility(View.GONE);
160 | imageView.setImageResource(android.R.drawable.presence_away);
161 | } else if (media.getState() == PBMedia.PBMediaState.SYNCED) {
162 | errorTextView.setVisibility(View.GONE);
163 | imageView.setImageResource(android.R.drawable.presence_online);
164 | } else if (media.getState() == PBMedia.PBMediaState.ERROR) {
165 | errorTextView.setText(media.getErrorMessage());
166 | imageView.setImageResource(android.R.drawable.presence_busy);
167 | }
168 |
169 | }
170 |
171 |
172 | @Override
173 | public int getCount() {
174 | int count = 0;
175 | try {
176 | count = filteredMedias.size();
177 | } catch (NullPointerException e) {
178 | Log.w(LOG_TAG, "count = " + count);
179 | Log.w(LOG_TAG, e);
180 | }
181 | return count;
182 | }
183 |
184 |
185 | @Override
186 | public PBMedia getItem(final int position) {
187 | PBMedia item = null;
188 | try {
189 | item = filteredMedias.get(position);
190 | } catch (NullPointerException e) {
191 | Log.w(LOG_TAG, e);
192 | }
193 | return item;
194 | }
195 |
196 |
197 | @Override
198 | public long getItemId(final int position) {
199 | return position;
200 | }
201 |
202 |
203 | ////////////////
204 | // Filterable //
205 | ////////////////
206 | @NonNull
207 | @Override
208 | public Filter getFilter() {
209 | if (filter == null) {
210 | filter = new Filter() {
211 |
212 | @SuppressWarnings("unchecked")
213 | @Override
214 | protected void publishResults(CharSequence constraint, FilterResults results) {
215 |
216 | filteredMedias = (List) results.values;
217 | notifyDataSetChanged();
218 | }
219 |
220 | @Override
221 | protected FilterResults performFiltering(CharSequence constraint) {
222 |
223 | final FilterResults results = new FilterResults();
224 | final List mediaList = getMediaList();
225 | results.values = mediaList;
226 | results.count = mediaList.size();
227 | return results;
228 | }
229 | };
230 | }
231 | return filter;
232 | }
233 |
234 |
235 | /////////////////////
236 | // Private methods //
237 | /////////////////////
238 | private List getMediaList() {
239 |
240 | filteredMedias.clear();
241 | final Boolean synced = preferences.getBoolean(PBMedia.PBMediaState.SYNCED.name(), true);
242 | final Boolean waiting = preferences.getBoolean(PBMedia.PBMediaState.WAITING.name(), true);
243 | final Boolean error = preferences.getBoolean(PBMedia.PBMediaState.ERROR.name(), true);
244 |
245 | for (int i = 0; i < medias.size(); i++) {
246 | final PBMedia media = medias.get(i);
247 | final boolean stateSynced = media.getState() == PBMedia.PBMediaState.SYNCED && synced;
248 | final boolean stateWaiting = media.getState() == PBMedia.PBMediaState.WAITING && waiting;
249 | final boolean stateError = media.getState() == PBMedia.PBMediaState.ERROR && error;
250 | if (stateSynced || stateWaiting || stateError) {
251 | filteredMedias.add(media);
252 | }
253 | }
254 | return filteredMedias;
255 | }
256 |
257 |
258 | // Getter //
259 | List getFilteredMedias() {
260 | return filteredMedias;
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/media/PBMedia.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.media;
20 |
21 | import android.content.Context;
22 | import android.content.SharedPreferences;
23 | import android.database.Cursor;
24 |
25 | import java.io.Serializable;
26 |
27 | import fr.s13d.photobackup.Log;
28 | import fr.s13d.photobackup.PBApplication;
29 |
30 |
31 | /**
32 | * Media object adding information over device medias.
33 | */
34 | public class PBMedia implements Serializable {
35 | private final int id;
36 | private final String path;
37 | private final long dateAdded;
38 | private final SharedPreferences mediasPreferences;
39 | private String errorMessage;
40 | private PBMediaState state;
41 |
42 | /**
43 | * Enumeration of all possible media states
44 | * - waiting: the media waits for being uploaded;
45 | * - synced: the media is synced with the server already;
46 | * - error: an error occurred.
47 | */
48 | public enum PBMediaState { WAITING, SYNCED, ERROR }
49 |
50 |
51 | //////////////////
52 | // Constructors //
53 | //////////////////
54 | PBMedia(Cursor mediaCursor) {
55 | this.id = mediaCursor.getInt(mediaCursor.getColumnIndexOrThrow("_id"));
56 | this.path = mediaCursor.getString(mediaCursor.getColumnIndexOrThrow("_data"));
57 | this.dateAdded = mediaCursor.getLong(mediaCursor.getColumnIndexOrThrow("date_added"));
58 | this.mediasPreferences = PBApplication.getApp().getSharedPreferences(PBApplication.PB_MEDIAS_SHARED_PREFS, Context.MODE_PRIVATE);
59 |
60 | // Find state from the shared preferences
61 | String stateString = mediasPreferences.getString(String.valueOf(this.id), PBMedia.PBMediaState.WAITING.name());
62 | this.state = PBMedia.PBMediaState.valueOf(stateString);
63 | this.errorMessage = "";
64 | }
65 |
66 |
67 | ////////////
68 | // Methods//
69 | ////////////
70 |
71 | // Will be used by the ArrayAdapter in the ListView
72 | @Override
73 | public String toString() {
74 | return "PBMedia: " + this.path;
75 | }
76 |
77 |
78 | /////////////////////////////////////////
79 | // Getters/Setters are the Java fun... //
80 | /////////////////////////////////////////
81 | public int getId() {
82 | return this.id;
83 | }
84 |
85 | public String getPath() {
86 | return this.path;
87 | }
88 |
89 | long getDateAdded() {
90 | return this.dateAdded;
91 | }
92 |
93 | public long getAge() {
94 | return System.currentTimeMillis() / 1000 - this.dateAdded;
95 | }
96 |
97 | public String getErrorMessage() { return this.errorMessage; }
98 |
99 | void setErrorMessage(String newMessage) { this.errorMessage = newMessage; }
100 |
101 | public PBMediaState getState() {
102 | return this.state;
103 | }
104 |
105 | public void setState(PBMediaState mediaState) {
106 | if (this.state != mediaState) {
107 | this.state = mediaState;
108 | mediasPreferences.edit().putString(String.valueOf(this.getId()), mediaState.name()).apply();
109 | Log.i("PBMedia", "Set state " + mediaState.toString() + " to " + this.getPath());
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/media/PBMediaSender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.media;
20 |
21 | import android.app.Notification;
22 | import android.app.NotificationManager;
23 | import android.app.PendingIntent;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.content.SharedPreferences;
27 | import android.graphics.Bitmap;
28 | import android.graphics.BitmapFactory;
29 | import android.net.ConnectivityManager;
30 | import android.net.NetworkInfo;
31 | import android.preference.PreferenceManager;
32 |
33 | import java.io.File;
34 | import java.io.IOException;
35 | import java.util.ArrayList;
36 | import java.util.List;
37 | import java.util.concurrent.TimeUnit;
38 |
39 | import fr.s13d.photobackup.Log;
40 | import fr.s13d.photobackup.PBActivity;
41 | import fr.s13d.photobackup.PBApplication;
42 | import fr.s13d.photobackup.PBConstants;
43 | import fr.s13d.photobackup.R;
44 | import fr.s13d.photobackup.interfaces.PBMediaSenderInterface;
45 | import fr.s13d.photobackup.preferences.PBServerPreferenceFragment;
46 | import okhttp3.Call;
47 | import okhttp3.Callback;
48 | import okhttp3.Credentials;
49 | import okhttp3.FormBody;
50 | import okhttp3.MediaType;
51 | import okhttp3.MultipartBody;
52 | import okhttp3.OkHttpClient;
53 | import okhttp3.Request;
54 | import okhttp3.RequestBody;
55 | import okhttp3.Response;
56 |
57 |
58 | /**
59 | * The sender of medias
60 | */
61 | public class PBMediaSender {
62 | private static final String LOG_TAG = "PBMediaSender";
63 | private static final String PASS_PARAM = "password";
64 | private static final String UPFILE_PARAM = "upfile";
65 | private static final String FILESIZE_PARAM = "filesize";
66 | private static final String TEST_PATH = "/test";
67 | private String serverUrl;
68 | private final SharedPreferences preferences;
69 | private final NotificationManager notificationManager;
70 | private String credentials;
71 | private Notification.Builder builder;
72 | private OkHttpClient okClient;
73 | private static final List interfaces = new ArrayList<>();
74 | private static int failureCount = 0;
75 | private static final int TIMEOUT_IN_SECONDS = 60;
76 |
77 |
78 | public PBMediaSender() {
79 | notificationManager = (NotificationManager) PBApplication.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
80 | preferences = PreferenceManager.getDefaultSharedPreferences(PBApplication.getApp());
81 | serverUrl = removeFinalSlashes(preferences.getString(PBServerPreferenceFragment.PREF_SERVER_URL, ""));
82 | }
83 |
84 |
85 | public void addInterface(PBMediaSenderInterface senderInterface) {
86 | interfaces.add(senderInterface);
87 | }
88 |
89 |
90 | ////////////////
91 | // Send media //
92 | ////////////////
93 | public void send(final PBMedia media, boolean manual) {
94 | // network
95 | final String wifiOnlyString = preferences.getString(PBConstants.PREF_WIFI_ONLY, PBApplication.getApp().getResources().getString(R.string.only_wifi_default));
96 | final boolean wifiOnly = wifiOnlyString.equals(PBApplication.getApp().getResources().getString(R.string.only_wifi));
97 | final ConnectivityManager cm = (ConnectivityManager) PBApplication.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
98 | final NetworkInfo info = cm.getActiveNetworkInfo();
99 | final boolean onWifi = info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI;
100 |
101 | // recently taken picture
102 | final String uploadRecentOnlyString = preferences.getString(PBConstants.PREF_RECENT_UPLOAD_ONLY, PBApplication.getApp().getResources().getString(R.string.only_recent_upload_default));
103 | final Boolean uploadRecentOnly = uploadRecentOnlyString.equals(PBApplication.getApp().getResources().getString(R.string.only_recent_upload));
104 | final Boolean recentPicture = (System.currentTimeMillis() / 1000 - media.getDateAdded()) < 600;
105 |
106 | Log.i(LOG_TAG, "Connectivity: onWifi=" + onWifi + ", wifiOnly=" + wifiOnly + ", recentPicture=" + recentPicture.toString());
107 | // test to send or not
108 | final boolean conditionToSend = (!wifiOnly || onWifi) && (!uploadRecentOnly || recentPicture);
109 | if (manual || conditionToSend) {
110 | sendMedia(media);
111 | }
112 | }
113 |
114 |
115 | private void sendMedia(final PBMedia media) {
116 | final MediaType mediaTypeJPG = MediaType.parse("image/jpg");
117 | final File upfile = new File(media.getPath());
118 | final RequestBody requestBody = new MultipartBody.Builder()
119 | .setType(MultipartBody.FORM)
120 | .addFormDataPart(PASS_PARAM, preferences.getString(PBServerPreferenceFragment.PREF_SERVER_PASS_HASH, ""))
121 | .addFormDataPart(FILESIZE_PARAM, String.valueOf(upfile.length()))
122 | .addFormDataPart(UPFILE_PARAM, upfile.getName(), RequestBody.create(mediaTypeJPG, upfile))
123 | .build();
124 | final Request request = makePostRequest(requestBody);
125 | getOkClient().newCall(request).enqueue(new Callback() {
126 | @Override
127 | public void onResponse(Call call, Response response) throws IOException {
128 | Log.i(LOG_TAG, "Get response with code " + response.code());
129 | if (response.code() == 200 || response.code() == 409) {
130 | sendDidSucceed(media);
131 | } else {
132 | sendDidFail(media, new Throwable(response.message()));
133 | }
134 | response.body().close();
135 | }
136 |
137 | @Override
138 | public void onFailure(Call call, IOException e) {
139 | Log.w(LOG_TAG, e.toString());
140 | sendDidFail(media, e);
141 | }
142 | });
143 | }
144 |
145 |
146 | ///////////////
147 | // Send test //
148 | ///////////////
149 | public void test() {
150 | final RequestBody requestBody = new FormBody.Builder()
151 | .add(PASS_PARAM, preferences.getString(PBServerPreferenceFragment.PREF_SERVER_PASS_HASH, ""))
152 | .build();
153 | final Request request = makePostRequest(requestBody, TEST_PATH);
154 | Log.i(LOG_TAG, "Initiating test call to " + request.url());
155 |
156 | getOkClient().newCall(request).enqueue(new Callback() {
157 | @Override
158 | public void onResponse(Call call, Response response) throws IOException {
159 | if (response.isSuccessful()) {
160 | testDidSucceed();
161 | } else if (response.isRedirect()) {
162 | prepareRedirectFollowing(response);
163 | test(); // rerun
164 | } else {
165 | testDidFail(response.message());
166 | }
167 | response.body().close();
168 | }
169 |
170 | @Override
171 | public void onFailure(Call call, IOException e) {
172 | testDidFail(e.getLocalizedMessage());
173 | Log.e(LOG_TAG, e);
174 | }
175 | });
176 | }
177 |
178 |
179 | /////////////////////
180 | // Private methods //
181 | /////////////////////
182 | private Request makePostRequest(RequestBody requestBody) {
183 | return makePostRequest(requestBody, "");
184 | }
185 |
186 |
187 | private Request makePostRequest(RequestBody requestBody, String pathSuffix) {
188 | final Request.Builder requestBuilder = new Request.Builder()
189 | .url(serverUrl + pathSuffix)
190 | .header("User-Agent", PBApplication.PB_USER_AGENT)
191 | .post(requestBody);
192 | if (credentials != null) {
193 | requestBuilder.header("Authorization", credentials);
194 | }
195 | return requestBuilder.build();
196 | }
197 |
198 |
199 | private void prepareRedirectFollowing(final Response response) {
200 | final String redirection = response.header("Location");
201 | Log.d(LOG_TAG, "Redirected to " + redirection);
202 | String newUrl = removeFinalSlashes(redirection).substring(0, redirection.length() - TEST_PATH.length());
203 | Log.d(LOG_TAG, "Update server url to " + newUrl);
204 | serverUrl = newUrl;
205 | preferences.edit().putString(PBServerPreferenceFragment.PREF_SERVER_URL, newUrl).apply();
206 | }
207 |
208 |
209 | private void createAuthCredentials() {
210 | // add HTTP Basic Auth to the client
211 | final String login = preferences.getString(PBServerPreferenceFragment.PREF_SERVER_HTTP_AUTH_LOGIN, "");
212 | final String pass = preferences.getString(PBServerPreferenceFragment.PREF_SERVER_HTTP_AUTH_PASS, "");
213 | if (preferences.getBoolean(PBServerPreferenceFragment.PREF_SERVER_HTTP_AUTH_SWITCH, false) &&
214 | !preferences.getString(PBServerPreferenceFragment.PREF_SERVER_HTTP_AUTH_LOGIN, "").isEmpty() &&
215 | !preferences.getString(PBServerPreferenceFragment.PREF_SERVER_HTTP_AUTH_PASS, "").isEmpty()) {
216 | credentials = Credentials.basic(login, pass);
217 | } else {
218 | credentials = null;
219 | }
220 | }
221 |
222 |
223 | private void sendDidSucceed(final PBMedia media) {
224 | media.setState(PBMedia.PBMediaState.SYNCED);
225 | media.setErrorMessage("");
226 | for (PBMediaSenderInterface senderInterface : interfaces) {
227 | senderInterface.onSendSuccess();
228 | }
229 | }
230 |
231 |
232 | private void sendDidFail(final PBMedia media, final Throwable e) {
233 | getNotificationBuilder().setSmallIcon(R.drawable.ic_error_outline_white_48dp);
234 | media.setState(PBMedia.PBMediaState.ERROR);
235 | media.setErrorMessage(e.getLocalizedMessage());
236 | for (PBMediaSenderInterface senderInterface : interfaces) {
237 | senderInterface.onSendFailure();
238 | }
239 | Log.w(LOG_TAG, e.toString());
240 | incrementFailureCount();
241 | updateNotificationText();
242 | }
243 |
244 |
245 | private void testDidSucceed() {
246 | for (final PBMediaSenderInterface senderInterface : interfaces) {
247 | senderInterface.onMessage(PBApplication.getApp().getResources().getString(R.string.toast_configuration_ok));
248 | senderInterface.onTestSuccess();
249 | }
250 | }
251 |
252 |
253 | private void testDidFail(final String failMessage) {
254 | final String message = PBApplication.getApp().getResources().getString(R.string.toast_configuration_ko) + " - (" + failMessage + ")";
255 | Log.d(LOG_TAG, message);
256 | for (final PBMediaSenderInterface senderInterface : interfaces) {
257 | senderInterface.onMessage(message);
258 | senderInterface.onTestFailure();
259 | }
260 | }
261 |
262 |
263 | private void updateNotificationText() {
264 | final String failureContent = PBApplication.getApp().getResources().getQuantityString(R.plurals.notif_failure, failureCount, failureCount);
265 | getNotificationBuilder().setContentText(failureContent);
266 | notificationManager.notify(0, getNotificationBuilder().build());
267 | }
268 |
269 |
270 | //////////////////
271 | // Lazy loaders //
272 | //////////////////
273 | private OkHttpClient getOkClient() {
274 | if (okClient == null) {
275 | okClient = new OkHttpClient.Builder()
276 | .readTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
277 | .connectTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
278 | .writeTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
279 | .build();
280 | createAuthCredentials();
281 | }
282 | return okClient;
283 | }
284 |
285 |
286 | private Notification.Builder getNotificationBuilder() {
287 | if (builder == null) {
288 | final Bitmap icon = BitmapFactory.decodeResource(PBApplication.getApp().getResources(), R.mipmap.ic_launcher);
289 | builder = new Notification.Builder(PBApplication.getApp());
290 | builder.setSmallIcon(R.drawable.ic_backup_white_48dp)
291 | .setContentTitle(PBApplication.getApp().getResources().getString(R.string.app_name))
292 | .setLargeIcon(icon);
293 |
294 | // add content intent to reopen the activity
295 | final Intent intent = new Intent(PBApplication.getApp(), PBActivity.class);
296 | intent.setAction(Intent.ACTION_MAIN);
297 | intent.addCategory(Intent.CATEGORY_LAUNCHER);
298 | final PendingIntent resultPendingIntent = PendingIntent.getActivity(PBApplication.getApp(), 0, intent, 0);
299 | builder.setContentIntent(resultPendingIntent);
300 | }
301 | return builder;
302 | }
303 |
304 |
305 | ///////////
306 | // Utils //
307 | ///////////
308 | private static String removeFinalSlashes(String s) {
309 | if (s == null || s.length() == 0) {
310 | return s;
311 | }
312 | int count = 0;
313 | int length = s.length();
314 | while (s.charAt(length - 1 - count) == '/') {
315 | count++;
316 | }
317 |
318 | return s.substring(0, s.length() - count);
319 | }
320 |
321 |
322 | private static void incrementFailureCount() {
323 | failureCount++;
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/media/PBMediaStore.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.media;
20 |
21 | import android.content.ContentResolver;
22 | import android.content.Context;
23 | import android.content.SharedPreferences;
24 | import android.database.Cursor;
25 | import android.database.MergeCursor;
26 | import android.net.Uri;
27 | import android.preference.PreferenceManager;
28 | import android.provider.MediaStore;
29 | import android.support.v4.util.ArrayMap;
30 | import android.text.TextUtils;
31 |
32 | import java.util.ArrayList;
33 | import java.util.List;
34 | import java.util.Set;
35 |
36 | import fr.s13d.photobackup.Log;
37 | import fr.s13d.photobackup.PBApplication;
38 | import fr.s13d.photobackup.PBConstants;
39 | import fr.s13d.photobackup.interfaces.PBMediaStoreInterface;
40 |
41 | import static android.preference.PreferenceManager.getDefaultSharedPreferences;
42 |
43 |
44 | public class PBMediaStore {
45 |
46 | private static final String LOG_TAG = "PBMediaStore";
47 | private static List mediaList;
48 | private static PBSyncMediaStoreTask syncTask;
49 | private static final SharedPreferences picturesPreferences = PBApplication.getApp()
50 | .getSharedPreferences(PBApplication.PB_MEDIAS_SHARED_PREFS, Context.MODE_PRIVATE);
51 | private final List interfaces = new ArrayList<>();
52 | private static final Uri imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
53 | private static final Uri videosUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
54 | private static final String DATE_ADDED_DESC = "date_added DESC";
55 | private static final String[] imagesProjection = new String[] { "_id", "_data", "date_added" };
56 |
57 |
58 | ////////////////
59 | // Life-cycle //
60 | ////////////////
61 | public void addInterface(PBMediaStoreInterface storeInterface) {
62 | interfaces.add(storeInterface);
63 | }
64 |
65 |
66 | public void removeInterface(PBMediaStoreInterface storeInterface) {
67 | interfaces.remove(storeInterface);
68 | }
69 |
70 |
71 | public void close() {
72 | if (syncTask != null) {
73 | syncTask.cancel(true);
74 | }
75 | setMediaListToNull();
76 | }
77 |
78 |
79 | /////////////
80 | // Queries //
81 | /////////////
82 | public PBMedia createMediaForLatestInStore(boolean backupVideos) {
83 | final ContentResolver cr = PBApplication.getApp().getContentResolver();
84 | final Cursor cursor = cr.query(backupVideos ? videosUri : imagesUri, null, null, null, DATE_ADDED_DESC);
85 | if (cursor == null || !cursor.moveToFirst()) {
86 | Log.d(LOG_TAG, "Media cursor is null or empty.");
87 | closeCursor(cursor);
88 | return null;
89 | }
90 |
91 | final int bucketId = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
92 | if (!backupVideos && !isBucketSelected(cursor.getString(bucketId))) {
93 | Log.d(LOG_TAG, "Media not in selected buckets.");
94 | closeCursor(cursor);
95 | return null;
96 | }
97 |
98 | final PBMedia media = new PBMedia(cursor);
99 | try {
100 | String stateString = picturesPreferences.getString(String.valueOf(media.getId()), PBMedia.PBMediaState.WAITING.name());
101 | media.setState(PBMedia.PBMediaState.valueOf(stateString));
102 | } catch (Exception e) {
103 | Log.e(LOG_TAG, e);
104 | }
105 | closeCursor(cursor);
106 |
107 | return media;
108 | }
109 |
110 |
111 | Cursor getAllMediasCursor() {
112 | String where = null;
113 | final SharedPreferences prefs = getDefaultSharedPreferences(PBApplication.getApp());
114 | final Set bucketIds = prefs.getStringSet(PBConstants.PREF_PICTURE_FOLDER_LIST, null);
115 | if (bucketIds != null && !bucketIds.isEmpty()) {
116 | final String bucketString = TextUtils.join(", ", bucketIds);
117 | where = "bucket_id in (" + bucketString + ")";
118 | }
119 |
120 | final boolean backupVideos = prefs.getBoolean(PBConstants.PREF_MEDIA_BACKUP_VIDEO, false);
121 | final ContentResolver cr = PBApplication.getApp().getContentResolver();
122 | final Cursor[] cursors = new Cursor[backupVideos ? 2 : 1];
123 | cursors[0] = cr.query(imagesUri, imagesProjection, where, null, DATE_ADDED_DESC);
124 | if (backupVideos) {
125 | cursors[1] = cr.query(videosUri, imagesProjection, where, null, DATE_ADDED_DESC);
126 | }
127 | if (cursors[0] == null) {
128 | Log.d(LOG_TAG, "Media cursor is null.");
129 | return null;
130 | }
131 |
132 | return new MergeCursor(cursors);
133 | }
134 |
135 |
136 | private boolean isBucketSelected(final String requestedBucketId) {
137 | Log.d(LOG_TAG, "Checking if bucket " + requestedBucketId + " is selected by user.");
138 | final SharedPreferences prefs = getDefaultSharedPreferences(PBApplication.getApp());
139 | final Set bucketSet = prefs.getStringSet(PBConstants.PREF_PICTURE_FOLDER_LIST, null);
140 | return bucketSet != null && bucketSet.contains(requestedBucketId);
141 | }
142 |
143 |
144 | private void getBucketData(final ArrayMap bucketNamesList, final String buckedId, final String buckedName) {
145 |
146 | // We want to group the images by bucket names. We abuse the
147 | // "WHERE" parameter to insert a "GROUP BY" clause into the SQL statement.
148 | // The template for "WHERE" parameter is like:
149 | // SELECT ... FROM ... WHERE (%s)
150 | // and we make it look like:
151 | // SELECT ... FROM ... WHERE (1) GROUP BY (2)
152 | // The "(1)" means true. The "(2)" means the second columns specified in projection.
153 | // Note that because there is a ")" in the template, we use "(2" to match it.
154 | final String[] projection = { buckedId, buckedName, "count(*) as media_count" };
155 | final String groupBy = "1) GROUP BY (2";
156 | final Cursor cursor = PBApplication.getApp().getContentResolver().query(imagesUri, projection, groupBy, null, "media_count desc");
157 |
158 | if (cursor != null && cursor.moveToFirst()) {
159 | String name;
160 | String id;
161 | String count;
162 | final int bucketIdColumn = cursor.getColumnIndex(buckedId);
163 | final int bucketNameColumn = cursor.getColumnIndex(buckedName);
164 | final int bucketCountColumn = cursor.getColumnIndex("media_count");
165 | do {
166 | id = cursor.getString(bucketIdColumn);
167 | name = cursor.getString(bucketNameColumn);
168 | count = cursor.getString(bucketCountColumn);
169 | bucketNamesList.put(id, name + " (" + count + ")");
170 | } while (cursor.moveToNext());
171 | }
172 | closeCursor(cursor);
173 |
174 | Log.d(LOG_TAG, bucketNamesList.toString());
175 | }
176 |
177 |
178 | /**
179 | * @return list of device media bucket names
180 | */
181 | public ArrayMap getBucketData() {
182 | final ArrayMap bucketNames = new ArrayMap<>();
183 | getBucketData(bucketNames, MediaStore.Images.ImageColumns.BUCKET_ID, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME);
184 |
185 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(PBApplication.getApp());
186 | final boolean backupVideos = sp.getBoolean(PBConstants.PREF_MEDIA_BACKUP_VIDEO, false);
187 | if (backupVideos) {
188 | getBucketData(bucketNames, MediaStore.Video.VideoColumns.BUCKET_ID, MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME);
189 | }
190 | return bucketNames;
191 | }
192 |
193 |
194 | private void closeCursor(Cursor cursor) {
195 | if (cursor != null && !cursor.isClosed()) {
196 | cursor.close();
197 | }
198 | }
199 |
200 |
201 | /////////////////////////////////
202 | // Synchronize the media store //
203 | /////////////////////////////////
204 | public void sync() {
205 | if (syncTask != null) {
206 | syncTask.cancel(true);
207 | }
208 |
209 | setSyncTask(new PBSyncMediaStoreTask());
210 | syncTask.execute();
211 | Log.i(LOG_TAG, "Start SyncMediaStoreTask");
212 | }
213 |
214 |
215 | void onPostSync() {
216 | for(PBMediaStoreInterface storeInterface : interfaces) {
217 | storeInterface.onSyncMediaStoreTaskPostExecute();
218 | }
219 | Log.i(LOG_TAG, "Stop PBSyncMediaStoreTask");
220 | }
221 |
222 |
223 | //////////////////
224 | // Lazy loaders //
225 | //////////////////
226 | public List getMediaList() {
227 | if (mediaList == null) {
228 | setMediaList(new ArrayList());
229 | }
230 | return mediaList;
231 | }
232 |
233 |
234 | ////////////////////
235 | // Static setters //
236 | ////////////////////
237 | private static void setMediaListToNull(){ mediaList = null; }
238 | private static void setSyncTask(PBSyncMediaStoreTask syncTask) { PBMediaStore.syncTask = syncTask; }
239 | private static void setMediaList(List mediaList) { PBMediaStore.mediaList = mediaList; }
240 | }
241 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/media/PBSyncMediaStoreTask.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.media;
20 |
21 | import android.content.Context;
22 | import android.content.SharedPreferences;
23 | import android.database.Cursor;
24 | import android.os.AsyncTask;
25 |
26 | import java.util.HashSet;
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.Set;
30 |
31 | import fr.s13d.photobackup.Log;
32 | import fr.s13d.photobackup.PBApplication;
33 |
34 |
35 | class PBSyncMediaStoreTask extends AsyncTask {
36 |
37 | private static final String LOG_TAG = "PBSyncMediaStoreTask";
38 | private final SharedPreferences picturesPreferences;
39 |
40 |
41 | /////////////////
42 | // Constructor //
43 | /////////////////
44 | PBSyncMediaStoreTask() {
45 | this.picturesPreferences = PBApplication.getApp().getSharedPreferences(PBApplication.PB_MEDIAS_SHARED_PREFS, Context.MODE_PRIVATE);
46 | }
47 |
48 |
49 | /////////////////////////////////
50 | // What makes you an AsyncTask //
51 | /////////////////////////////////
52 | protected Void doInBackground(Void... voids) {
53 |
54 | // Get all known pictures in PB
55 | final Map mediasMap = picturesPreferences.getAll();
56 | final Set inCursor = new HashSet<>();
57 |
58 | // Get all pictures on device
59 | Cursor cursor = null;
60 | try {
61 | cursor = PBApplication.getMediaStore().getAllMediasCursor();
62 | } catch (SecurityException e) {
63 | Log.d(LOG_TAG, e);
64 | }
65 |
66 | // loop through them to sync
67 | PBMedia media;
68 | String stateString;
69 | PBMedia.PBMediaState state;
70 | final List mediaList = PBApplication.getMediaStore().getMediaList();
71 | mediaList.clear();
72 | while (cursor != null && cursor.moveToNext()) {
73 | if(isCancelled()) {
74 | Log.i(LOG_TAG, "PBSyncMediaStoreTask cancelled");
75 | return null;
76 | }
77 | // create new media
78 | media = new PBMedia(cursor);
79 | stateString = (String)mediasMap.get(Integer.toString(media.getId()));
80 | state = (stateString != null) ? PBMedia.PBMediaState.valueOf(stateString) : PBMedia.PBMediaState.WAITING;
81 | media.setState(state);
82 | mediaList.add(media); // populate list
83 | inCursor.add(Integer.toString(media.getId()));
84 | }
85 | if (cursor != null && !cursor.isClosed()) {
86 | cursor.close();
87 | }
88 |
89 | // purge pictures in preferences that were removed from device
90 | final Set inCursorCopy = new HashSet<>(inCursor);
91 | final Set inPreferences = new HashSet<>(mediasMap.keySet());
92 | inPreferences.removeAll(inCursor);
93 | inCursor.removeAll(inCursorCopy);
94 | inPreferences.addAll(inCursor);
95 |
96 | for (final String key : inPreferences) {
97 | Log.d(LOG_TAG, "Remove media " + key + " from preference");
98 | picturesPreferences.edit().remove(key).apply();
99 | }
100 |
101 | return null;
102 | }
103 |
104 | protected void onPostExecute(Void result) {
105 | PBApplication.getMediaStore().onPostSync();
106 | }
107 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/preferences/PBPreferenceFragment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package fr.s13d.photobackup.preferences;
21 |
22 | import android.Manifest;
23 | import android.app.Activity;
24 | import android.app.ActivityManager;
25 | import android.content.ComponentName;
26 | import android.content.Context;
27 | import android.content.Intent;
28 | import android.content.ServiceConnection;
29 | import android.content.SharedPreferences;
30 | import android.content.pm.PackageManager;
31 | import android.os.Build;
32 | import android.os.Bundle;
33 | import android.os.IBinder;
34 | import android.preference.ListPreference;
35 | import android.preference.MultiSelectListPreference;
36 | import android.preference.Preference;
37 | import android.preference.PreferenceFragment;
38 | import android.preference.PreferenceManager;
39 | import android.preference.PreferenceScreen;
40 | import android.preference.SwitchPreference;
41 | import android.support.v4.app.ActivityCompat;
42 | import android.support.v4.content.ContextCompat;
43 | import android.support.v4.util.ArrayMap;
44 | import android.text.TextUtils;
45 | import android.view.Menu;
46 | import android.view.MenuInflater;
47 | import android.view.MenuItem;
48 | import android.webkit.URLUtil;
49 | import android.widget.Toast;
50 |
51 | import java.util.ArrayList;
52 | import java.util.Arrays;
53 | import java.util.Map;
54 | import java.util.Set;
55 |
56 | import fr.s13d.photobackup.BuildConfig;
57 | import fr.s13d.photobackup.Log;
58 | import fr.s13d.photobackup.PBActivity;
59 | import fr.s13d.photobackup.PBApplication;
60 | import fr.s13d.photobackup.PBConstants;
61 | import fr.s13d.photobackup.media.PBMediaSender;
62 | import fr.s13d.photobackup.PBService;
63 | import fr.s13d.photobackup.R;
64 | import fr.s13d.photobackup.about.PBAboutActivity;
65 | import fr.s13d.photobackup.interfaces.PBMediaSenderInterface;
66 | import fr.s13d.photobackup.interfaces.PBMediaStoreInterface;
67 | import fr.s13d.photobackup.journal.PBJournalActivity;
68 |
69 |
70 | public class PBPreferenceFragment extends PreferenceFragment
71 | implements SharedPreferences.OnSharedPreferenceChangeListener,
72 | PBMediaStoreInterface, PBMediaSenderInterface {
73 |
74 | private static final String LOG_TAG = "PBPreferenceFragment";
75 | private PBService currentService;
76 | private final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(PBApplication.getApp());
77 | private ArrayMap bucketNames;
78 | public static final int PERMISSION_READ_EXTERNAL_STORAGE = 0;
79 | private int permissionOrigin;
80 |
81 |
82 | // binding
83 | private boolean isBoundToService = false;
84 | private final ServiceConnection serviceConnection = new ServiceConnection() {
85 |
86 | public void onServiceConnected(ComponentName className, IBinder binder) {
87 | PBService.Binder b = (PBService.Binder) binder;
88 | currentService = b.getService();
89 | updatePreferences(); // update journal serverKeys number
90 | Log.i(LOG_TAG, "Connected to service");
91 | }
92 |
93 | public void onServiceDisconnected(ComponentName className) {
94 | Log.i(LOG_TAG, "Disconnected to service");
95 | }
96 | };
97 |
98 |
99 | //////////////
100 | // Override //
101 | //////////////
102 | @Override
103 | public void onCreate(final Bundle savedInstanceState) {
104 | super.onCreate(savedInstanceState);
105 | migratePreferences();
106 |
107 | // create preferences
108 | addPreferencesFromResource(R.xml.preferences);
109 |
110 | // add click listeners to launch activities on click
111 | // Intents were historically defined into preferences.xml but as build variants cannot
112 | // inject applicationId as intent packageName properly, it was done this way now...
113 | setOnClickListener(PBConstants.PREF_ABOUT, PBAboutActivity.class);
114 | setOnClickListener(PBConstants.PREF_UPLOAD_JOURNAL, PBJournalActivity.class);
115 |
116 | final PreferenceScreen mediasPreferenceScreen = (PreferenceScreen) findPreference(PBConstants.PREF_MEDIAS_TO_BACKUP);
117 | mediasPreferenceScreen.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
118 | @Override
119 | public boolean onPreferenceClick(Preference preference) {
120 | checkPermissions(PBConstants.PERM_ORIGIN_MEDIAS);
121 | return true;
122 | }
123 | });
124 | setHasOptionsMenu(true);
125 | }
126 |
127 |
128 | private void setOnClickListener(final String preferenceKey, final Class cls) {
129 | final Preference uploadJournalPref = findPreference(preferenceKey);
130 | uploadJournalPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
131 | public boolean onPreferenceClick(Preference preference) {
132 | final Intent intent = new Intent(PBPreferenceFragment.this.getActivity(), cls);
133 | startActivity(intent);
134 | return true;
135 | }
136 | });
137 | }
138 |
139 |
140 | @Override
141 | public void onResume() {
142 | super.onResume();
143 |
144 | initPreferences();
145 | preferences.registerOnSharedPreferenceChangeListener(this);
146 | PBApplication.getMediaStore().addInterface(this);
147 |
148 | if (isPhotoBackupServiceRunning()) {
149 | Intent intent = new Intent(this.getActivity(), PBService.class);
150 | getActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
151 | isBoundToService = true;
152 | }
153 |
154 | updatePreferences();
155 | try {
156 | fillBuckets();
157 | } catch (SecurityException e) {
158 | Log.d(LOG_TAG, e);
159 | }
160 |
161 | }
162 |
163 |
164 | @Override
165 | public void onPause() {
166 | super.onPause();
167 | preferences.unregisterOnSharedPreferenceChangeListener(this);
168 | PBApplication.getMediaStore().removeInterface(this);
169 |
170 | if (isPhotoBackupServiceRunning() && isBoundToService) {
171 | getActivity().unbindService(serviceConnection);
172 | isBoundToService = false;
173 | }
174 | }
175 |
176 |
177 | @Override
178 | public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
179 |
180 | Log.i(LOG_TAG, "onSharedPreferenceChanged: " + key);
181 | if (key.equals(PBConstants.PREF_SERVICE_RUNNING)) {
182 | startOrStopService(sharedPreferences);
183 |
184 | } else if (key.equals(PBConstants.PREF_PICTURE_FOLDER_LIST) || key.equals(PBConstants.PREF_MEDIA_BACKUP_VIDEO)) {
185 | PBApplication.getMediaStore().sync();
186 | }
187 | setSummaries();
188 | }
189 |
190 |
191 | @Override
192 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
193 | inflater.inflate(R.menu.main_menu, menu);
194 | menu.findItem(R.id.menu_upload_all).setVisible(isPhotoBackupServiceRunning());
195 | super.onCreateOptionsMenu(menu, inflater);
196 | }
197 |
198 |
199 | @Override
200 | public boolean onOptionsItemSelected(MenuItem item) {
201 | if (item.getItemId() == R.id.menu_upload_all) {
202 | currentService.sendNextMedia();
203 | Toast.makeText(getActivity(), R.string.toast_upload_all, Toast.LENGTH_SHORT).show();
204 | return true;
205 | }
206 | return super.onOptionsItemSelected(item);
207 | }
208 |
209 |
210 | /////////////////////
211 | // private methods //
212 | /////////////////////
213 | private void migratePreferences() {
214 | Map allPrefs = preferences.getAll();
215 | if (allPrefs.containsKey(PBConstants.PREF_WIFI_ONLY)) {
216 | Object obj = allPrefs.get(PBConstants.PREF_WIFI_ONLY);
217 | if (obj instanceof Boolean) {
218 | Log.i(LOG_TAG, "Migrating PREF_WIFI_ONLY for v0.7.0");
219 | Boolean bool = preferences.getBoolean(PBConstants.PREF_WIFI_ONLY, false);
220 | String wifiOnlyString = bool ? getString(R.string.only_wifi) : getString(R.string.not_only_wifi);
221 | SharedPreferences.Editor editor = preferences.edit();
222 | editor.putString(PBConstants.PREF_WIFI_ONLY, wifiOnlyString).apply();
223 | Log.i(LOG_TAG, "Migration done!");
224 | }
225 | }
226 |
227 | }
228 |
229 |
230 | private void initPreferences() {
231 | ((PBActivity) getActivity()).setActionBar();
232 |
233 | // switch on if service is running
234 | final SwitchPreference switchPreference = (SwitchPreference) findPreference(PBConstants.PREF_SERVICE_RUNNING);
235 | switchPreference.setChecked(isPhotoBackupServiceRunning());
236 |
237 | // initiate preferences summaries
238 | setSummaries();
239 | }
240 |
241 |
242 | private void setSummaries() {
243 | final String wifiOnly = preferences.getString(PBConstants.PREF_WIFI_ONLY,
244 | getResources().getString(R.string.only_wifi_default)); // default
245 | final ListPreference wifiPreference = (ListPreference) findPreference(PBConstants.PREF_WIFI_ONLY);
246 | wifiPreference.setSummary(wifiOnly);
247 |
248 | final String recentUploadOnly = preferences.getString(PBConstants.PREF_RECENT_UPLOAD_ONLY,
249 | getResources().getString(R.string.only_recent_upload_default)); // default
250 | final ListPreference recentUploadPreference = (ListPreference) findPreference(PBConstants.PREF_RECENT_UPLOAD_ONLY);
251 | recentUploadPreference.setSummary(recentUploadOnly);
252 |
253 | final String serverUrl = preferences.getString(PBServerPreferenceFragment.PREF_SERVER_URL, null);
254 | if (serverUrl != null) {
255 | final String serverName = preferences.getString(PBConstants.PREF_SERVER, null);
256 | if (serverName != null) {
257 | final PBServerListPreference serverPreference = (PBServerListPreference) findPreference(PBConstants.PREF_SERVER);
258 | serverPreference.setSummary(serverName + " @ " + serverUrl);
259 |
260 | // bonus: left icon of the server
261 | final int serverNamesId = getResources().getIdentifier("pref_server_names", "array", getActivity().getPackageName());
262 | final String[] serverNames = getResources().getStringArray(serverNamesId);
263 | final int serverPosition = Arrays.asList(serverNames).indexOf(serverName);
264 | final int serverIconsId = getResources().getIdentifier("pref_server_icons", "array", getActivity().getPackageName());
265 | final String[] serverIcons = getResources().getStringArray(serverIconsId);
266 | final String serverIcon = serverIcons[serverPosition];
267 | final String[] parts = serverIcon.split("\\.");
268 | final String drawableName = parts[parts.length - 1];
269 | final int id = getResources().getIdentifier(drawableName, "drawable", getActivity().getPackageName());
270 | if (id != 0) {
271 | serverPreference.setIcon(id);
272 | }
273 | }
274 | }
275 |
276 | String bucketSummary = "";
277 | final Set selectedBuckets = preferences.getStringSet(PBConstants.PREF_PICTURE_FOLDER_LIST, null);
278 | if (selectedBuckets != null && bucketNames != null) {
279 | final ArrayList selectedBucketNames = new ArrayList<>();
280 | for (String entry : selectedBuckets) {
281 | String oneName = bucketNames.get(entry);
282 | if (oneName != null) {
283 | oneName = oneName.substring(0, oneName.lastIndexOf('(') - 1);
284 | selectedBucketNames.add(oneName);
285 | }
286 | }
287 | bucketSummary = TextUtils.join(", ", selectedBucketNames);
288 | }
289 | final MultiSelectListPreference bucketListPreference = (MultiSelectListPreference) findPreference(PBConstants.PREF_PICTURE_FOLDER_LIST);
290 | bucketListPreference.setSummary(bucketSummary);
291 | }
292 |
293 |
294 | private void startOrStopService(final SharedPreferences preferences) {
295 | boolean userDidStart = preferences.getBoolean(PBConstants.PREF_SERVICE_RUNNING, false);
296 | Log.i(LOG_TAG, "PREF_SERVICE_RUNNING = " + userDidStart);
297 |
298 | if (userDidStart) {
299 | if (validatePreferences()) {
300 | checkPermissions(PBConstants.PERM_ORIGIN_SERVICE);
301 | } else {
302 | final SwitchPreference switchPreference = (SwitchPreference) findPreference(PBConstants.PREF_SERVICE_RUNNING);
303 | switchPreference.setChecked(false);
304 | }
305 | } else if (isPhotoBackupServiceRunning() && currentService != null) {
306 | Log.i(LOG_TAG, "stop PhotoBackup service");
307 | getActivity().unbindService(serviceConnection);
308 | getActivity().invalidateOptionsMenu();
309 | isBoundToService = false;
310 | currentService.stopSelf();
311 | currentService = null;
312 | updatePreferences();
313 | }
314 | }
315 |
316 |
317 | private void checkPermissions(final int permOrigin) {
318 | permissionOrigin = permOrigin;
319 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
320 | int permissionCheck = ContextCompat.checkSelfPermission(getContext(),
321 | Manifest.permission.READ_EXTERNAL_STORAGE);
322 | if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
323 | ActivityCompat.requestPermissions(getActivity(),
324 | new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
325 | PERMISSION_READ_EXTERNAL_STORAGE);
326 | } else {
327 | Log.i(LOG_TAG, "Permission READ_EXTERNAL_STORAGE already given, cool!");
328 | didGrantPermission(); // next step
329 | }
330 | } else { // for older Android versions, continue without asking permission
331 | didGrantPermission();
332 | }
333 | }
334 |
335 |
336 | public void didGrantPermission() {
337 | if (permissionOrigin == PBConstants.PERM_ORIGIN_SERVICE) {
338 | testMediaSender();
339 | } else if (permissionOrigin == PBConstants.PERM_ORIGIN_MEDIAS) {
340 | fillBuckets();
341 | }
342 | }
343 |
344 |
345 | private boolean validatePreferences() {
346 | String serverUrl = preferences.getString(PBServerPreferenceFragment.PREF_SERVER_URL, "");
347 | if (!URLUtil.isValidUrl(serverUrl) || serverUrl.isEmpty()) {
348 | Toast.makeText(getActivity(), R.string.toast_urisyntaxexception, Toast.LENGTH_LONG).show();
349 | return false;
350 | }
351 |
352 | String serverPassHash = preferences.getString(PBServerPreferenceFragment.PREF_SERVER_PASS_HASH, "");
353 | if (serverPassHash.isEmpty()) {
354 | Toast.makeText(getActivity(), R.string.toast_serverpassempty, Toast.LENGTH_LONG).show();
355 | return false;
356 | }
357 |
358 | return true;
359 | }
360 |
361 |
362 | // Returns the current state of the PhotoBackup Service
363 | // See http://stackoverflow.com/a/5921190/417006
364 | private boolean isPhotoBackupServiceRunning() {
365 | final Activity activity = getActivity();
366 | if (activity != null) {
367 | final ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
368 | for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
369 | boolean sameClass = PBService.class.getName().equals(service.service.getClassName());
370 | boolean samePackage = BuildConfig.APPLICATION_ID.equals(service.service.getPackageName());
371 | if (sameClass && samePackage) {
372 | Log.i(LOG_TAG, "Service is running");
373 | return true;
374 | }
375 | }
376 | }
377 | Log.i(LOG_TAG, "Service or activity is NOT running");
378 | return false;
379 | }
380 |
381 |
382 | private void updatePreferences() {
383 | try {
384 | final Preference uploadJournalPref = findPreference(PBConstants.PREF_UPLOAD_JOURNAL);
385 | // service is running
386 | if (isPhotoBackupServiceRunning() && currentService != null) {
387 | int nbMedia = PBApplication.getMediaStore().getMediaList().size();
388 | uploadJournalPref.setTitle(this.getResources().getString(R.string.journal_title) + " (" + nbMedia + ")");
389 | uploadJournalPref.setEnabled(nbMedia > 0);
390 | // service is not running
391 | } else {
392 | uploadJournalPref.setTitle(getResources().getString(R.string.journal_noaccess));
393 | uploadJournalPref.setEnabled(false);
394 | }
395 | } catch (IllegalStateException e) {
396 | Log.w(LOG_TAG, e);
397 | }
398 | }
399 |
400 |
401 | private void fillBuckets() {
402 | this.bucketNames = PBApplication.getMediaStore().getBucketData();
403 |
404 | final CharSequence[] ids = this.bucketNames.values().toArray(new CharSequence[this.bucketNames.size()]);
405 | final CharSequence[] names = this.bucketNames.keySet().toArray(new CharSequence[this.bucketNames.size()]);
406 |
407 | final MultiSelectListPreference bucketListPreference = (MultiSelectListPreference) findPreference(PBConstants.PREF_PICTURE_FOLDER_LIST);
408 | bucketListPreference.setEntries(ids);
409 | bucketListPreference.setEnabled(true);
410 | bucketListPreference.setEntryValues(names);
411 |
412 | setSummaries();
413 | }
414 |
415 |
416 | ////////////////////
417 | // public methods //
418 | ////////////////////
419 | private void testMediaSender() {
420 | Log.i(LOG_TAG, "start PhotoBackup service");
421 | PBMediaSender mediaSender = new PBMediaSender();
422 | mediaSender.addInterface(this);
423 | mediaSender.test();
424 | }
425 |
426 |
427 | ////////////////////////////////////
428 | // PBMediaStoreListener callbacks //
429 | ////////////////////////////////////
430 | public void onSyncMediaStoreTaskPostExecute() {
431 | updatePreferences();
432 | }
433 |
434 |
435 | ///////////////////////////////////
436 | // PBMediaSenderEvents callbacks //
437 | ///////////////////////////////////
438 | public void onMessage(final String message) {
439 | getActivity().runOnUiThread(new Runnable() {
440 | @Override
441 | public void run() {
442 | Toast.makeText(PBApplication.getApp(), message, Toast.LENGTH_SHORT).show();
443 | }
444 | });
445 | }
446 |
447 |
448 | public void onTestSuccess() {
449 | final Intent serviceIntent = new Intent(getActivity(), PBService.class);
450 | getActivity().startService(serviceIntent);
451 | getActivity().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
452 | isBoundToService = true;
453 | getActivity().invalidateOptionsMenu();
454 | }
455 |
456 |
457 | public void onTestFailure() {
458 | getActivity().runOnUiThread(new Runnable() {
459 | @Override
460 | public void run() {
461 | final SwitchPreference switchPreference = (SwitchPreference) findPreference(PBConstants.PREF_SERVICE_RUNNING);
462 | switchPreference.setChecked(false);
463 | }
464 | });
465 | }
466 |
467 |
468 | public void onSendSuccess() {
469 | // Do nothing
470 | }
471 | public void onSendFailure() {
472 | // Do nothing
473 | }
474 | }
475 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/preferences/PBServerListPreference.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.preferences;
20 |
21 | import android.app.Activity;
22 | import android.app.AlertDialog;
23 | import android.app.Dialog;
24 | import android.app.FragmentTransaction;
25 | import android.content.Context;
26 | import android.content.DialogInterface;
27 | import android.content.SharedPreferences;
28 | import android.os.Bundle;
29 | import android.preference.ListPreference;
30 | import android.preference.PreferenceManager;
31 | import android.util.AttributeSet;
32 | import android.view.LayoutInflater;
33 | import android.view.View;
34 | import android.view.ViewGroup;
35 | import android.widget.BaseAdapter;
36 | import android.widget.ImageView;
37 | import android.widget.TextView;
38 |
39 | import fr.s13d.photobackup.Log;
40 | import fr.s13d.photobackup.PBApplication;
41 | import fr.s13d.photobackup.PBConstants;
42 | import fr.s13d.photobackup.R;
43 |
44 | public class PBServerListPreference extends ListPreference {
45 |
46 | private static final String LOG_TAG = "PBServerListPreference";
47 | private final Context context;
48 | private CharSequence[] servers;
49 |
50 |
51 | //////////////////
52 | // Constructors //
53 | //////////////////
54 | public PBServerListPreference(Context theContext, AttributeSet attrs) {
55 | super(theContext, attrs);
56 | context = theContext;
57 | }
58 |
59 |
60 | //////////////
61 | // Override //
62 | //////////////
63 | @Override
64 | protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
65 | servers = getEntryValues();
66 |
67 | if (servers == null) {
68 | throw new IllegalStateException("ListPreference requires an entryValues array!");
69 | }
70 |
71 | ListPreferenceAdapter listPreferenceAdapter = new ListPreferenceAdapter();
72 | builder.setPositiveButton(null, null);
73 | builder.setAdapter(listPreferenceAdapter, new DialogInterface.OnClickListener() {
74 | public void onClick(DialogInterface dialog, int which) {
75 | Log.i(LOG_TAG, "clicked");
76 | }
77 | });
78 | }
79 |
80 |
81 | /////////////
82 | // private //
83 | /////////////
84 | private class ListPreferenceAdapter extends BaseAdapter {
85 | ListPreferenceAdapter() {
86 | // Do nothing
87 | }
88 |
89 | public int getCount() {
90 | return servers.length;
91 | }
92 |
93 | public Object getItem(int position) {
94 | return position;
95 | }
96 |
97 | public long getItemId(int position) {
98 | return position;
99 | }
100 |
101 | public View getView(final int position, View view, ViewGroup parent) {
102 | View row = view;
103 | if (row == null) {
104 | row = LayoutInflater.from(context).inflate(R.layout.server_list_row, parent, false);
105 | RowHolder holder = new RowHolder(position, row);
106 |
107 | row.setTag(holder);
108 | row.setOnClickListener(new View.OnClickListener() {
109 | public void onClick(View v) {
110 | Log.i(LOG_TAG, "clicked on: " + servers[position]);
111 | clickedAtPosition(position);
112 |
113 | Dialog mDialog = getDialog();
114 | mDialog.dismiss();
115 | }
116 | });
117 | }
118 |
119 | return row;
120 | }
121 |
122 |
123 | private void clickedAtPosition(int position) {
124 | // store the preference as the position
125 | final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(PBApplication.getApp());
126 | preferences.edit().putString(PBConstants.PREF_SERVER, servers[position].toString()).apply();
127 |
128 | // build new preference fragment from server position in list
129 | Bundle fragmentArguments = new Bundle(1);
130 | fragmentArguments.putString(PBServerPreferenceFragment.PREF_SERVER_NAME,
131 | servers[position].toString());
132 | PBServerPreferenceFragment fragment = new PBServerPreferenceFragment();
133 | fragment.setArguments(fragmentArguments);
134 |
135 | // put it on back stack
136 | final Activity activity = (Activity) context;
137 | FragmentTransaction transaction = activity.getFragmentManager().beginTransaction();
138 | transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
139 | transaction.replace(android.R.id.content, fragment);
140 | transaction.addToBackStack(null);
141 | transaction.commit();
142 | }
143 |
144 |
145 | class RowHolder {
146 | private TextView text = null;
147 |
148 | RowHolder(int position, View row) {
149 | text = (TextView) row.findViewById(R.id.servername);
150 | text.setText(servers[position]);
151 | setRowIcon(position, row);
152 | }
153 |
154 | private void setRowIcon(final int position, final View row) {
155 | final int arrayId = context.getResources().getIdentifier("pref_server_icons", "array", context.getPackageName());
156 | final String[] stringArray = context.getResources().getStringArray(arrayId);
157 | final String drawablePath = stringArray[position];
158 | final String[] parts = drawablePath.split("\\.");
159 | final String drawableName = parts[parts.length - 1];
160 | final int drawableId = context.getResources().getIdentifier(drawableName,
161 | "drawable", context.getPackageName());
162 | if (drawableId != 0) {
163 | final ImageView imageView = (ImageView) row.findViewById(R.id.thumbnail);
164 | imageView.setImageResource(drawableId);
165 | }
166 | }
167 |
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/s13d/photobackup/preferences/PBServerPreferenceFragment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2016 Stéphane Péchard.
3 | *
4 | * This file is part of PhotoBackup.
5 | *
6 | * PhotoBackup is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * PhotoBackup is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | package fr.s13d.photobackup.preferences;
20 |
21 | import android.content.SharedPreferences;
22 | import android.os.Bundle;
23 | import android.preference.EditTextPreference;
24 | import android.preference.Preference;
25 | import android.preference.PreferenceFragment;
26 | import android.preference.PreferenceManager;
27 | import android.preference.PreferenceScreen;
28 | import android.view.LayoutInflater;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 |
32 | import java.security.MessageDigest;
33 | import java.security.NoSuchAlgorithmException;
34 |
35 | import fr.s13d.photobackup.Log;
36 | import fr.s13d.photobackup.R;
37 |
38 |
39 | public class PBServerPreferenceFragment extends PreferenceFragment
40 | implements SharedPreferences.OnSharedPreferenceChangeListener {
41 |
42 | private static final String LOG_TAG = "PBServerPreferenceFragment";
43 | private String serverName;
44 | private SharedPreferences preferences;
45 |
46 | public static final String PREF_SERVER_NAME = "PREF_SERVER_NAME";
47 | public static final String PREF_SERVER_URL = "PREF_SERVER_URL";
48 | private static final String PREF_SERVER_PASS = "PREF_SERVER_PASS";
49 | public static final String PREF_SERVER_PASS_HASH = "PREF_SERVER_PASS_HASH";
50 | public static final String PREF_SERVER_HTTP_AUTH_SWITCH = "PREF_SERVER_HTTP_AUTH_SWITCH";
51 | public static final String PREF_SERVER_HTTP_AUTH_LOGIN = "PREF_SERVER_HTTP_AUTH_LOGIN";
52 | public static final String PREF_SERVER_HTTP_AUTH_PASS = "PREF_SERVER_HTTP_AUTH_PASS";
53 |
54 |
55 | //////////////////
56 | // Constructors //
57 | //////////////////
58 | public PBServerPreferenceFragment() {
59 | // Do nothing
60 | }
61 |
62 |
63 | //////////////
64 | // Override //
65 | //////////////
66 | @Override
67 | public void onCreate(final Bundle savedInstanceState) {
68 | super.onCreate(savedInstanceState);
69 | addPreferencesFromResource(R.xml.server_preferences);
70 |
71 | preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
72 | }
73 |
74 |
75 | @Override
76 | public void onResume() {
77 | super.onResume();
78 | preferences.registerOnSharedPreferenceChangeListener(this);
79 | setSummaries();
80 | }
81 |
82 |
83 | @Override
84 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
85 | final View view = super.onCreateView(inflater, container, savedInstanceState);
86 |
87 | final Bundle bundle = getArguments();
88 | if (bundle!= null && bundle.containsKey(PREF_SERVER_NAME)) {
89 | serverName = bundle.getString(PREF_SERVER_NAME);
90 | configurePreference();
91 | }
92 | return view;
93 | }
94 |
95 |
96 | @Override
97 | public void onPause() {
98 | super.onPause();
99 | if (preferences != null) {
100 | preferences.unregisterOnSharedPreferenceChangeListener(this);
101 | }
102 | }
103 |
104 |
105 | @Override
106 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
107 |
108 | Log.i(LOG_TAG, "onSharedPreferenceChanged: " + key);
109 |
110 | setSummaries();
111 | if (key.equals(PREF_SERVER_PASS)) {
112 | final String pass = sharedPreferences.getString(PREF_SERVER_PASS, "");
113 | if (!pass.isEmpty()) {
114 | createAndSetServerPass(sharedPreferences);
115 | }
116 |
117 | } else if (sharedPreferences == null) {
118 | Log.e(LOG_TAG, "Error: preferences == null");
119 | }
120 |
121 | }
122 |
123 |
124 | /////////////////////
125 | // Private methods //
126 | /////////////////////
127 | private void configurePreference() {
128 | // title and back button of the action bar
129 | getActivity().setTitle(serverName + " server settings");
130 | if (getActivity().getActionBar() != null) {
131 | getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
132 | }
133 |
134 | // save server name into the preferences
135 | final SharedPreferences.Editor preferencesEditor = preferences.edit();
136 | preferencesEditor.putString(PREF_SERVER_NAME, serverName).apply();
137 |
138 | // access configuration in servers_params.xml file
139 | final int arrayId = getActivity().getResources().getIdentifier(serverName, "array", getActivity().getPackageName());
140 | final String[] serverPrefsToRemove = getActivity().getResources().getStringArray(arrayId);
141 |
142 | // remove unused preferences given in xml list
143 | final PreferenceScreen screen = (PreferenceScreen) findPreference(LOG_TAG);
144 | for (String param : serverPrefsToRemove) {
145 | Log.i(LOG_TAG, "Remove preference named: " + param);
146 | final Preference pref = findPreference(param);
147 | screen.removePreference(pref);
148 | }
149 |
150 | }
151 |
152 |
153 | private void setSummaries() {
154 | final EditTextPreference urlPreference = (EditTextPreference) findPreference(PREF_SERVER_URL);
155 | urlPreference.setSummary(preferences.getString(PREF_SERVER_URL, this.getResources().getString(R.string.server_url_summary)));
156 |
157 | final String serverPassHash = preferences.getString(PREF_SERVER_PASS_HASH, "");
158 | final EditTextPreference serverPassTextPreference = (EditTextPreference) findPreference(PREF_SERVER_PASS);
159 | if (serverPassHash.isEmpty()) {
160 | serverPassTextPreference.setSummary(getResources().getString(R.string.server_password_summary));
161 | } else {
162 | serverPassTextPreference.setSummary(getResources().getString(R.string.server_password_summary_set));
163 | }
164 |
165 | final EditTextPreference httpLoginPreference = (EditTextPreference) findPreference(PREF_SERVER_HTTP_AUTH_LOGIN);
166 | httpLoginPreference.setSummary(preferences.getString(PREF_SERVER_HTTP_AUTH_LOGIN, ""));
167 |
168 | final String httpPass = preferences.getString(PREF_SERVER_HTTP_AUTH_PASS,"");
169 | if (!httpPass.isEmpty()) {
170 | final EditTextPreference httpPassPreference = (EditTextPreference) findPreference(PREF_SERVER_HTTP_AUTH_PASS);
171 | httpPassPreference.setSummary(getResources().getString(R.string.server_password_summary_set));
172 | }
173 | }
174 |
175 |
176 | private void createAndSetServerPass(final SharedPreferences sharedPreferences) {
177 | // store only the hash of the password in the preferences
178 | MessageDigest md;
179 | try {
180 | md = MessageDigest.getInstance("SHA-512");
181 | } catch (NoSuchAlgorithmException e) {
182 | Log.e(LOG_TAG, e);
183 | return;
184 | }
185 |
186 | final String pass = sharedPreferences.getString(PREF_SERVER_PASS, null);
187 | if (pass == null) {
188 | return;
189 | }
190 |
191 | // compute the hash
192 | md.update(pass.getBytes());
193 | final byte[] mb = md.digest();
194 | final StringBuilder sbHash = new StringBuilder();
195 | for (final byte temp : mb) {
196 | final StringBuilder sb = new StringBuilder(Integer.toHexString(temp));
197 | if (sb.length() == 0) {
198 | sb.insert(0, "00");
199 | } else if (sb.length() == 1) {
200 | sb.insert(0, '0');
201 | }
202 | sbHash.append(sb.substring(sb.length() - 2));
203 | }
204 |
205 | final SharedPreferences.Editor preferencesEditor = preferences.edit();
206 | // set the hash in the preferences
207 | preferencesEditor.putString(PREF_SERVER_PASS_HASH, sbHash.toString()).apply();
208 | // remove the real password from the preferences, for security
209 | preferencesEditor.putString(PREF_SERVER_PASS, "").apply();
210 | }
211 |
212 | }
213 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_backup_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-hdpi/ic_backup_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_done_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-hdpi/ic_done_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_error_outline_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-hdpi/ic_error_outline_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_file_upload_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-hdpi/ic_file_upload_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_backup_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-mdpi/ic_backup_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_done_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-mdpi/ic_done_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_error_outline_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-mdpi/ic_error_outline_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_file_upload_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-mdpi/ic_file_upload_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_backup_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xhdpi/ic_backup_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_done_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xhdpi/ic_done_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_error_outline_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xhdpi/ic_error_outline_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_file_upload_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xhdpi/ic_file_upload_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_backup_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxhdpi/ic_backup_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_done_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxhdpi/ic_done_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_error_outline_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxhdpi/ic_error_outline_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_file_upload_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxhdpi/ic_file_upload_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_backup_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxxhdpi/ic_backup_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_done_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxxhdpi/ic_done_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_error_outline_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxxhdpi/ic_error_outline_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_file_upload_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable-xxxhdpi/ic_file_upload_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/drawable/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
20 |
21 |
27 |
28 |
36 |
37 |
44 |
45 |
48 |
49 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_journal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
28 |
29 |
43 |
44 |
58 |
59 |
60 |
61 |
65 |
66 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
15 |
16 |
26 |
27 |
38 |
39 |
46 |
47 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/server_list_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
15 |
16 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PhotoBackup
5 | PhotoBackup verze %1$s
6 |
7 | Služba
8 | PhotoBackup běží.
9 | PhotoBackup neběží.
10 |
11 | Nastavení serveru
12 | No server
13 | Adresa serveru
14 | Adresa serveru.
15 | Heslo serveru
16 | Heslo serveru.
17 | Heslo je nastaveno.
18 |
19 | Záloha
20 | Pouze přes Wi-Fi
21 | Přes Wi-Fi nebo mobilní připojení
22 | @string/only_wifi
23 |
24 | Nahrávat obrázky
25 | Pouze nové
26 | Všechny obrázky v přístroji
27 | @string/only_recent_upload
28 |
29 | Neplatná adresa serveru!
30 | Heslo nesmí být prázdné!
31 | Výborně, nastavení serveru je funkční!
32 | Test serveru selhal, ověřte prosím svou konfiguraci.
33 | Bez oprávnění číst obrázky nemůže PhotoBackup správně pracovat.
34 |
35 | Informace
36 | Log nahrávání
37 | Spustit servis pro přístup k logu.
38 | Uloženo
39 | Čekání
40 | Chyba
41 |
42 | O aplikaci
43 | PhotoBackup pro Android zálohuje obrázky z tohoto přístroje na váš domácí server. Tímto způsobem zůstáváte jediným vlastníkem a uživatelem svých fotografií. Pro instalaci a nastavení serveru si prohlédněte:
44 | Open source licence
45 |
46 | Vaše fotografie jsou zálohovány na váš server.
47 |
48 | %d chyba
49 | %d chyba
50 | %d chyby
51 |
52 |
53 | Zrušit
54 | Jméno fotografie
55 | Náhled fotografie
56 | Manuální záloha
57 | Ihned uložit fotografii!
58 |
59 | Adresáře k zálohování
60 | Nahrát všechny
61 | Začal nahrát všechny obrázky.
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PhotoBackup
4 | PhotoBackup für Android sichert die Bilder dieses Gerätes auf einen Server deiner Wahl.
5 | Auf diese Weise bleibst hat niemand anderes Zugriff zu den Photos.
6 | Um deinen eigenen Server zu installieren und konfigurieren, schaue bitte hier:
7 | Über
8 | Open-Source-Lizenzen
9 |
10 | PhotoBackup Version %1$s
11 | Abbrechen
12 | Informationen
13 | Fertig
14 | Fehler
15 | Starte erst den Dienst um das Protokoll zu sehen
16 | Upload Protokoll
17 | Warten
18 | Dieses Bild jetzt hochladen
19 | Manuelles Hochladen
20 | Name des Fotos
21 | Alle Fotos dieses Gerätes
22 | Über WLAN und mobile Datenverbindung
23 | Das Photo wird auf den Server hochgeladen.
24 | Nur neue Photos
25 | Welche Fotos hochladen
26 | Nur neue Photos
27 | Nur über WLAN
28 | Nur über WLAN
29 | Sicherung
30 | Passwort des Servers
31 | Das Passwort wurde gesetzt.
32 | Passwort
33 | Kein Server
34 | Server konfigurieren
35 | URL des Servers
36 | Adresse
37 | PhotoBackup wurde nicht gestartet.
38 | PhotoBackup läuft.
39 | Dienst
40 | Vorschau des Fotos
41 | Server Test fehlgeschlagen. Bitte überprüfe die Konfiguration.
42 | Server Test erfolgreich!
43 | PhotoBackup kann ohne die Erlaubnis auf Medien zuzugreifen keine Fotos hochladen.
44 | Passwort darf nicht leer sein!
45 | Die Serveradresse ist ungültig.
46 |
47 | Ordner zur Sicherung auswählen
48 | Laden Sie alle
49 | Gestartet um alle Bilder zu laden.
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PhotoBackup
5 | PhotoBackup version %1$s
6 |
7 | Service
8 | PhotoBackup est actif.
9 | PhotoBackup est inactif.
10 |
11 | Configurer un serveur
12 | Aucun serveur
13 | Adresse
14 | L\'adresse du serveur où sont envoyées vos photos.
15 | Mot de passe
16 | Le mot de passe du serveur.
17 | Le mot de passe est renseigné.
18 |
19 | Sauvegarde
20 | Via Wi-Fi uniquement
21 | Via Wi-Fi ou réseau mobile
22 | @string/only_wifi
23 |
24 | Photos à sauvegarder
25 | Seulement les nouvelles
26 | Toutes les photos de l\'appareil
27 | @string/only_recent_upload
28 |
29 | L\'adresse du serveur est invalide !
30 | Le mot de passe ne peut pas être vide !
31 | La configuration du serveur est bonne.
32 | Le test du serveur a échoué, merci de vérifier sa configuration.
33 | PhotoBackup ne peut pas fonctionner correctement sans permission d\'accès à vos photos.
34 |
35 | Informations
36 | Journal d\'envoi
37 | Activez le service pour voir le journal.
38 | Sauvegardé
39 | En attente
40 | Erreur
41 | Tout sauvegarder
42 |
43 | À propos
44 | PhotoBackup pour Android sauvegarde les photos de cet appareil
45 | sur votre serveur personnel. Ainsi, vous restez l\'unique propriétaire et utilisateur
46 | de vos photographies. Pour installer et configurer un serveur, voir :
47 | Licences open source
48 |
49 | Votre photo est en cours de sauvegarde.
50 |
51 | %d échec
52 | %d échecs
53 |
54 |
55 | Annuler
56 | Nom de la photo
57 | Vignette de la photo
58 | Sauvegarde manuelle
59 | Sauvegarder cette photo maintenant !
60 |
61 | Répertoires à sauvegarder
62 | Sauvegarde de toutes les photos démarrée.
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PhotoBackup
5 | PhotoBackup version %1$s
6 |
7 | サービス
8 | PhotoBackup は実行しています。
9 | PhotoBackup は実行していません。
10 |
11 | サーバーの設定
12 | サーバーはありません
13 | アドレス
14 | サーバーのアドレス。
15 | パスワード
16 | サーバーのパスワード。
17 | パスワードを設定しました。
18 |
19 | バックアップ
20 | Wi-Fi 接続時のみ
21 | Wi-Fi 接続またはモバイルネットワーク
22 | @string/only_wifi
23 |
24 | アップロードする写真
25 | 新しい撮影のみ
26 | デバイスのすべての写真
27 | @string/only_recent_upload
28 |
29 | サーバーアドレスが正しくありません!
30 | パスワードは空にできません!
31 | サーバーは正しく設定されています!
32 | サーバーのテストに失敗しました。設定を確認してください。
33 | PhotoBackup は写真を読み取るアクセス許可がないと正しく動作できません。
34 | 開始して、写真をアップロードします。
35 |
36 | 情報
37 | アップロードジャーナル
38 | サービスを起動して、ジャーナルにアクセスします。
39 | 保存しました
40 | 待機しています
41 | エラー
42 | すべてアップロード
43 |
44 | アプリについて
45 | PhotoBackup for Android は自宅のサーバーにこのデバイスの
46 | 写真をバックアップします。 この方法は、あなたが写真の唯一の所有者で使用者です。
47 | サーバーのインストールと設定方法は、こちらをご覧ください:
48 | オープンソース ライセンス
49 |
50 | 写真をサーバーにバックアップしています。
51 |
52 | %d 失敗
53 | %d 失敗
54 |
55 |
56 | キャンセル
57 | 写真の名前
58 | 写真のサムネイル
59 | 手動バックアップ
60 | 今すぐ写真を保存します!
61 |
62 | バックアップするフォルダー
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PhotoBackup
7 |
8 |
9 |
10 | R.mipmap.ic_launcher
11 |
12 |
13 |
14 |
15 |
16 | @string/only_wifi
17 | @string/not_only_wifi
18 |
19 |
20 | @string/only_wifi
21 | @string/not_only_wifi
22 |
23 |
24 |
25 |
26 | @string/only_recent_upload
27 | @string/not_only_recent_upload
28 |
29 |
30 | @string/only_recent_upload
31 | @string/not_only_recent_upload
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/values/servers_params.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 | PREF_SERVER_LOGIN
23 |
24 |
25 |
26 | PREF_SERVER_BASICAUTH
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PhotoBackup
5 | PhotoBackup version %1$s
6 |
7 | Service
8 | PhotoBackup is running.
9 | PhotoBackup is not running.
10 |
11 | Configure a server
12 | No server
13 | Address
14 | The address of the server.
15 | Password
16 | The password of the server.
17 | The password is set.
18 |
19 | Backup
20 | Over Wi-Fi only
21 | Over Wi-Fi or mobile network
22 | @string/only_wifi
23 |
24 | Pictures to upload
25 | Newly taken only
26 | All pictures of the device
27 | @string/only_recent_upload
28 |
29 | The server address is invalid!
30 | The password cannot be empty!
31 | Server configuration is valid, great!
32 | Server test failed, please verify your configuration.
33 | PhotoBackup cannot work properly without the permission to read your pictures.
34 | Started to upload all pictures.
35 |
36 | Informations
37 | Upload journal
38 | Launch the service to access the journal.
39 | Saved
40 | Waiting
41 | Error
42 | Upload all
43 |
44 | About
45 | PhotoBackup for Android backups the pictures of this device
46 | on your home server. This way, you are the unique owner and user of your pictures.
47 | To install and configure a server, see:
48 | Open source licenses
49 |
50 | Your photo is being backed up on your server.
51 |
52 | %d failure
53 | %d failures
54 |
55 |
56 | Cancel
57 | Name of the photo
58 | Thumbnail of the photo
59 | Manual backup
60 | Save this picture now!
61 |
62 | Folders to backup
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
21 |
22 |
29 |
30 |
33 |
34 |
41 |
42 |
48 |
49 |
55 |
56 |
57 |
60 |
61 |
65 |
66 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/server_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
23 |
24 |
27 |
28 |
34 |
35 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | token: 0f71e8cd-b2e6-4da7-924a-4c815b7923b7
3 | branch: master
4 |
5 | coverage:
6 | precision: 2
7 | round: down
8 | range: "70...100"
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhotoBackup/client-android/2d632b8012082585a7d9570a4265fa441bfcd878/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 24 21:37:03 CEST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------