├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── httpfilemanager │ │ ├── index.html │ │ ├── index.js │ │ └── js │ │ └── jquery-1.11.0.js │ ├── java │ └── com │ │ └── sjl │ │ └── rfm │ │ ├── App.java │ │ ├── CoreService.java │ │ ├── MainActivity.java │ │ ├── ServerManager.java │ │ ├── component │ │ ├── AppConfig.java │ │ ├── AppExceptionResolver.java │ │ ├── AppMessageConverter.java │ │ ├── LoggerInterceptor.java │ │ └── LoginInterceptor.java │ │ ├── controller │ │ ├── FileManagerController.java │ │ └── PageController.java │ │ ├── model │ │ ├── FileInfo.java │ │ └── ReturnData.java │ │ └── util │ │ ├── FileUtils.java │ │ ├── JsonUtils.java │ │ ├── Logger.java │ │ ├── NetUtils.java │ │ └── TimeUtils.java │ └── res │ ├── drawable │ ├── bg_normal.xml │ ├── bg_pressed.xml │ └── btn_selector.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-v14 │ └── styles.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── example1.png └── example2.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /.idea/ 3 | /build/ 4 | .gradle 5 | /local.properties 6 | .DS_Store 7 | /captures 8 | .externalNativeBuild 9 | /maven-repo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RemoteFileManager 2 | 3 | 这是基于HTTP服务实现的Android远程文件管理器,在局域网内可以轻松在电脑端远程管理手机文件,支持文件浏览、分类排序和下载 4 | 5 | # 效果图 6 | 客户端: 7 | 8 | 9 | 10 | PC端: 11 | 12 | 13 | 14 | # 感谢 15 | 16 | [https://github.com/yanzhenjie/AndServer](https://github.com/yanzhenjie/AndServer "https://github.com/yanzhenjie/AndServer") 17 | 18 | # 使用 19 | 20 | ## 使用步骤 21 | 22 | 1. 检查手机端和PC端是否在同一局域网 23 | 2. 手机端启动HTTP服务 24 | 3. 在PC端输入http地址即可管理文件 25 | 26 | 27 | # License 28 | 29 | Copyright 2019 Song Jiali 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: rootProject.ext.plugins.android 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.android.compileSdkVersion 5 | buildToolsVersion rootProject.ext.android.buildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId rootProject.ext.android.applicationId 9 | minSdkVersion rootProject.ext.android.sampleMinSdkVersion 10 | targetSdkVersion rootProject.ext.android.sampleTargetSdkVersion 11 | versionCode rootProject.ext.android.versionCode 12 | versionName rootProject.ext.android.versionName 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_8 17 | targetCompatibility JavaVersion.VERSION_1_8 18 | } 19 | 20 | buildTypes { 21 | debug { 22 | debuggable true 23 | zipAlignEnabled false 24 | minifyEnabled false 25 | shrinkResources false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | 29 | release { 30 | debuggable false 31 | zipAlignEnabled true 32 | minifyEnabled true 33 | shrinkResources true 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation rootProject.ext.dependencies.design 41 | implementation rootProject.ext.dependencies.appCompat 42 | implementation rootProject.ext.dependencies.loading 43 | implementation rootProject.ext.dependencies.json 44 | implementation rootProject.ext.dependencies.commonsLang 45 | implementation rootProject.ext.dependencies.commonsCollections 46 | implementation 'com.yanzhenjie.andserver:api:2.0.5' 47 | annotationProcessor 'com.yanzhenjie.andserver:processor:2.0.5' 48 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -ignorewarnings 3 | -dontusemixedcaseclassnames 4 | -dontskipnonpubliclibraryclasses 5 | -dontskipnonpubliclibraryclassmembers 6 | -useuniqueclassmembernames 7 | -allowaccessmodification 8 | -dontpreverify 9 | -verbose 10 | -dontoptimize 11 | -renamesourcefileattribute SourceFile 12 | -keepattributes SourceFile,LineNumberTable 13 | -keepattributes Signature 14 | -keepattributes *Annotation* 15 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 16 | -keepclassmembers class * extends android.app.Activity { 17 | public void *(android.view.View); 18 | } 19 | -keepclassmembers enum * { 20 | public static **[] values(); 21 | public static ** valueOf(java.lang.String); 22 | } 23 | -keep public class **.R$*{ 24 | public static final int *; 25 | } 26 | -keepclassmembers class **.R$* { 27 | public static ; 28 | } 29 | -keepclassmembers class * { 30 | native ; 31 | } 32 | -keepclasseswithmembernames class * { 33 | native ; 34 | } 35 | -keepclasseswithmembers class * { 36 | public (android.content.Context); 37 | public (android.content.Context, android.util.AttributeSet); 38 | public (android.content.Context, android.util.AttributeSet, int); 39 | public void set*(***); 40 | public *** set*(***); 41 | public *** get*(***); 42 | public *** get*(); 43 | } 44 | -keep class android.support.annotation.Keep 45 | -keep @android.support.annotation.Keep class * {*;} 46 | -keepclasseswithmembers class * { 47 | @android.support.annotation.Keep ; 48 | } 49 | -keepclasseswithmembers class * { 50 | @android.support.annotation.Keep ; 51 | } 52 | -keepclasseswithmembers class * { 53 | @android.support.annotation.Keep (...); 54 | } 55 | -keep public class * implements android.os.Parcelable{*;} 56 | -keepclasseswithmembers class * implements android.os.Parcelable { 57 | public static final android.os.Parcelable$Creator *; 58 | } 59 | 60 | -dontwarn com.alibaba.fastjson.** 61 | -keep class com.alibaba.fastjson.**{*;} -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/assets/httpfilemanager/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 67 | 68 | 69 | 70 | 71 | 72 | 糟糕!Google Chrome无法解读服务器所发送的数据。请 73 | 报告错误,并附上 74 | 原始列表。 75 | 76 | LOCATION的索引 77 | 78 | 79 | 80 | [上级目录] 81 | 82 | 83 | 84 | 85 | 86 | 87 | 名称 88 | 89 | 大小 90 | 91 | 92 | 修改日期 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /app/src/main/assets/httpfilemanager/index.js: -------------------------------------------------------------------------------- 1 | function addRow(name, url, isdir, 2 | size, size_string, date_modified, date_modified_string) { 3 | if(name == "." || name == "..") 4 | return; 5 | 6 | var root = document.location.pathname; 7 | if(root.substr(-1) !== "/") 8 | root += "/"; 9 | 10 | var tbody = document.getElementById("tbody"); 11 | var row = document.createElement("tr"); 12 | var file_cell = document.createElement("td"); 13 | var link = document.createElement("a"); 14 | 15 | link.className = isdir ? "icon dir" : "icon file"; 16 | 17 | if(isdir) { 18 | name = name + "/"; 19 | url = url + "/"; 20 | size = 0; 21 | size_string = ""; 22 | } else { 23 | link.draggable = "true"; 24 | link.addEventListener("dragstart", onDragStart, false); 25 | } 26 | link.innerText = name; 27 | link.href = root + url; 28 | 29 | file_cell.dataset.value = name; 30 | file_cell.appendChild(link); 31 | 32 | row.appendChild(file_cell); 33 | row.appendChild(createCell(size, size_string)); 34 | row.appendChild(createCell(date_modified, date_modified_string)); 35 | 36 | tbody.appendChild(row); 37 | } 38 | 39 | function onDragStart(e) { 40 | var el = e.srcElement; 41 | var name = el.innerText.replace(":", ""); 42 | var download_url_data = "application/octet-stream:" + name + ":" + el.href; 43 | e.dataTransfer.setData("DownloadURL", download_url_data); 44 | e.dataTransfer.effectAllowed = "copy"; 45 | } 46 | 47 | function createCell(value, text) { 48 | var cell = document.createElement("td"); 49 | cell.setAttribute("class", "detailsColumn"); 50 | cell.dataset.value = value; 51 | cell.innerText = text; 52 | return cell; 53 | } 54 | 55 | function start(location) { 56 | var header = document.getElementById("header"); 57 | header.innerText = header.innerText.replace("LOCATION", location); 58 | 59 | document.getElementById("title").innerText = header.innerText; 60 | } 61 | 62 | function onHasParentDirectory() { 63 | var box = document.getElementById("parentDirLinkBox"); 64 | box.style.display = "block"; 65 | 66 | var root = document.location.pathname; 67 | if(!root.endsWith("/")) 68 | root += "/"; 69 | 70 | var link = document.getElementById("parentDirLink"); 71 | link.href = root + ".."; 72 | } 73 | 74 | function onListingParsingError() { 75 | var box = document.getElementById("listingParsingErrorBox"); 76 | box.innerHTML = box.innerHTML.replace("LOCATION", encodeURI(document.location) + 77 | "?raw"); 78 | box.style.display = "block"; 79 | } 80 | 81 | function sortTable(column) { 82 | var theader = document.getElementById("theader"); 83 | var oldOrder = theader.cells[column].dataset.order || '1'; 84 | oldOrder = parseInt(oldOrder, 10) 85 | var newOrder = 0 - oldOrder; 86 | theader.cells[column].dataset.order = newOrder; 87 | 88 | var tbody = document.getElementById("tbody"); 89 | var rows = tbody.rows; 90 | var list = [], 91 | i; 92 | for(i = 0; i < rows.length; i++) { 93 | list.push(rows[i]); 94 | } 95 | 96 | list.sort(function(row1, row2) { 97 | var a = row1.cells[column].dataset.value; 98 | var b = row2.cells[column].dataset.value; 99 | if(column) { 100 | a = parseInt(a, 10); 101 | b = parseInt(b, 10); 102 | return a > b ? newOrder : a < b ? oldOrder : 0; 103 | } 104 | 105 | // Column 0 is text. 106 | if(a > b) 107 | return newOrder; 108 | if(a < b) 109 | return oldOrder; 110 | return 0; 111 | }); 112 | 113 | // Appending an existing child again just moves it. 114 | for(i = 0; i < list.length; i++) { 115 | tbody.appendChild(list[i]); 116 | } 117 | } 118 | 119 | // Add event handlers to column headers. 120 | function addHandlers(element, column) { 121 | element.onclick = (e) => sortTable(column); 122 | element.onkeydown = (e) => { 123 | if(e.key == 'Enter' || e.key == ' ') { 124 | sortTable(column); 125 | e.preventDefault(); 126 | } 127 | }; 128 | } 129 | 130 | function onLoad() { 131 | addHandlers(document.getElementById('nameColumnHeader'), 0); 132 | addHandlers(document.getElementById('sizeColumnHeader'), 1); 133 | addHandlers(document.getElementById('dateColumnHeader'), 2); 134 | } 135 | 136 | window.addEventListener('DOMContentLoaded', onLoad); 137 | 138 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. 139 | // Use of this source code is governed by a BSD-style license that can be 140 | // found in the LICENSE file. 141 | 142 | /** 143 | * @fileoverview This file defines a singleton which provides access to all data 144 | * that is available as soon as the page's resources are loaded (before DOM 145 | * content has finished loading). This data includes both localized strings and 146 | * any data that is important to have ready from a very early stage (e.g. things 147 | * that must be displayed right away). 148 | * 149 | * Note that loadTimeData is not guaranteed to be consistent between page 150 | * refreshes (https://crbug.com/740629) and should not contain values that might 151 | * change if the page is re-opened later. 152 | */ 153 | 154 | // #import {assert} from './assert.m.js'; 155 | // #import {parseHtmlSubset} from './parse_html_subset.m.js'; 156 | 157 | /** 158 | * @typedef {{ 159 | * substitutions: (Array|undefined), 160 | * attrs: (Object|undefined), 161 | * tags: (Array|undefined), 162 | * }} 163 | */ 164 | /* #export */ 165 | let SanitizeInnerHtmlOpts; 166 | 167 | // eslint-disable-next-line no-var 168 | /* #export */ 169 | /** @type {!LoadTimeData} */ 170 | var loadTimeData; 171 | 172 | // Expose this type globally as a temporary work around until 173 | // https://github.com/google/closure-compiler/issues/544 is fixed. 174 | /** @constructor */ 175 | function LoadTimeData() {} 176 | 177 | (function() { 178 | 'use strict'; 179 | 180 | LoadTimeData.prototype = { 181 | /** 182 | * Sets the backing object. 183 | * 184 | * Note that there is no getter for |data_| to discourage abuse of the form: 185 | * 186 | * var value = loadTimeData.data()['key']; 187 | * 188 | * @param {Object} value The de-serialized page data. 189 | */ 190 | set data(value) { 191 | expect(!this.data_, 'Re-setting data.'); 192 | this.data_ = value; 193 | }, 194 | 195 | /** 196 | * Returns a JsEvalContext for |data_|. 197 | * @returns {JsEvalContext} 198 | */ 199 | createJsEvalContext: function() { 200 | return new JsEvalContext(this.data_); 201 | }, 202 | 203 | /** 204 | * @param {string} id An ID of a value that might exist. 205 | * @return {boolean} True if |id| is a key in the dictionary. 206 | */ 207 | valueExists: function(id) { 208 | return id in this.data_; 209 | }, 210 | 211 | /** 212 | * Fetches a value, expecting that it exists. 213 | * @param {string} id The key that identifies the desired value. 214 | * @return {*} The corresponding value. 215 | */ 216 | getValue: function(id) { 217 | expect(this.data_, 'No data. Did you remember to include strings.js?'); 218 | const value = this.data_[id]; 219 | expect(typeof value != 'undefined', 'Could not find value for ' + id); 220 | return value; 221 | }, 222 | 223 | /** 224 | * As above, but also makes sure that the value is a string. 225 | * @param {string} id The key that identifies the desired string. 226 | * @return {string} The corresponding string value. 227 | */ 228 | getString: function(id) { 229 | const value = this.getValue(id); 230 | expectIsType(id, value, 'string'); 231 | return /** @type {string} */ (value); 232 | }, 233 | 234 | /** 235 | * Returns a formatted localized string where $1 to $9 are replaced by the 236 | * second to the tenth argument. 237 | * @param {string} id The ID of the string we want. 238 | * @param {...(string|number)} var_args The extra values to include in the 239 | * formatted output. 240 | * @return {string} The formatted string. 241 | */ 242 | getStringF: function(id, var_args) { 243 | const value = this.getString(id); 244 | if(!value) { 245 | return ''; 246 | } 247 | 248 | const args = Array.prototype.slice.call(arguments); 249 | args[0] = value; 250 | return this.substituteString.apply(this, args); 251 | }, 252 | 253 | /** 254 | * Make a string safe for use with with Polymer bindings that are 255 | * inner-h-t-m-l (or other innerHTML use). 256 | * @param {string} rawString The unsanitized string. 257 | * @param {SanitizeInnerHtmlOpts=} opts Optional additional allowed tags and 258 | * attributes. 259 | * @return {string} 260 | */ 261 | sanitizeInnerHtml: function(rawString, opts) { 262 | opts = opts || {}; 263 | return parseHtmlSubset('' + rawString + '', opts.tags, opts.attrs) 264 | .firstChild.innerHTML; 265 | }, 266 | 267 | /** 268 | * Returns a formatted localized string where $1 to $9 are replaced by the 269 | * second to the tenth argument. Any standalone $ signs must be escaped as 270 | * $$. 271 | * @param {string} label The label to substitute through. 272 | * This is not an resource ID. 273 | * @param {...(string|number)} var_args The extra values to include in the 274 | * formatted output. 275 | * @return {string} The formatted string. 276 | */ 277 | substituteString: function(label, var_args) { 278 | const varArgs = arguments; 279 | return label.replace(/\$(.|$|\n)/g, function(m) { 280 | assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.'); 281 | return m == '$$' ? '$' : varArgs[m[1]]; 282 | }); 283 | }, 284 | 285 | /** 286 | * Returns a formatted string where $1 to $9 are replaced by the second to 287 | * tenth argument, split apart into a list of pieces describing how the 288 | * substitution was performed. Any standalone $ signs must be escaped as $$. 289 | * @param {string} label A localized string to substitute through. 290 | * This is not an resource ID. 291 | * @param {...(string|number)} var_args The extra values to include in the 292 | * formatted output. 293 | * @return {!Array} The formatted 294 | * string pieces. 295 | */ 296 | getSubstitutedStringPieces: function(label, var_args) { 297 | const varArgs = arguments; 298 | // Split the string by separately matching all occurrences of $1-9 and of 299 | // non $1-9 pieces. 300 | const pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) || []).map(function(p) { 301 | // Pieces that are not $1-9 should be returned after replacing $$ 302 | // with $. 303 | if(!p.match(/^\$[1-9]$/)) { 304 | assert( 305 | (p.match(/\$/g) || []).length % 2 == 0, 306 | 'Unescaped $ found in localized string.'); 307 | return { 308 | value: p.replace(/\$\$/g, '$'), 309 | arg: null 310 | }; 311 | } 312 | 313 | // Otherwise, return the substitution value. 314 | return { 315 | value: varArgs[p[1]], 316 | arg: p 317 | }; 318 | }); 319 | 320 | return pieces; 321 | }, 322 | 323 | /** 324 | * As above, but also makes sure that the value is a boolean. 325 | * @param {string} id The key that identifies the desired boolean. 326 | * @return {boolean} The corresponding boolean value. 327 | */ 328 | getBoolean: function(id) { 329 | const value = this.getValue(id); 330 | expectIsType(id, value, 'boolean'); 331 | return /** @type {boolean} */ (value); 332 | }, 333 | 334 | /** 335 | * As above, but also makes sure that the value is an integer. 336 | * @param {string} id The key that identifies the desired number. 337 | * @return {number} The corresponding number value. 338 | */ 339 | getInteger: function(id) { 340 | const value = this.getValue(id); 341 | expectIsType(id, value, 'number'); 342 | expect(value == Math.floor(value), 'Number isn\'t integer: ' + value); 343 | return /** @type {number} */ (value); 344 | }, 345 | 346 | /** 347 | * Override values in loadTimeData with the values found in |replacements|. 348 | * @param {Object} replacements The dictionary object of keys to replace. 349 | */ 350 | overrideValues: function(replacements) { 351 | expect( 352 | typeof replacements == 'object', 353 | 'Replacements must be a dictionary object.'); 354 | for(const key in replacements) { 355 | this.data_[key] = replacements[key]; 356 | } 357 | } 358 | }; 359 | 360 | /** 361 | * Checks condition, displays error message if expectation fails. 362 | * @param {*} condition The condition to check for truthiness. 363 | * @param {string} message The message to display if the check fails. 364 | */ 365 | function expect(condition, message) { 366 | if(!condition) { 367 | console.error( 368 | 'Unexpected condition on ' + document.location.href + ': ' + message); 369 | } 370 | } 371 | 372 | /** 373 | * Checks that the given value has the given type. 374 | * @param {string} id The id of the value (only used for error message). 375 | * @param {*} value The value to check the type on. 376 | * @param {string} type The type we expect |value| to be. 377 | */ 378 | function expectIsType(id, value, type) { 379 | expect( 380 | typeof value == type, '[' + value + '] (' + id + ') is not a ' + type); 381 | } 382 | 383 | expect(!loadTimeData, 'should only include this file once'); 384 | loadTimeData = new LoadTimeData; 385 | 386 | // Expose |loadTimeData| directly on |window|. This is only necessary by the 387 | // auto-generated load_time_data.m.js, since within a JS module the scope is 388 | // local. 389 | window.loadTimeData = loadTimeData; 390 | })(); -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/App.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm; 17 | 18 | import android.app.Application; 19 | import android.content.Context; 20 | import android.os.Environment; 21 | 22 | import com.sjl.rfm.util.FileUtils; 23 | import com.yanzhenjie.andserver.util.IOUtils; 24 | 25 | import java.io.File; 26 | 27 | import androidx.annotation.NonNull; 28 | 29 | /** 30 | * Created by Zhenjie Yan on 2018/6/9. 31 | */ 32 | public class App extends Application { 33 | 34 | private static App mInstance; 35 | 36 | private File mRootDir; 37 | 38 | @Override 39 | public void onCreate() { 40 | super.onCreate(); 41 | 42 | if (mInstance == null) { 43 | mInstance = this; 44 | initRootPath(this); 45 | } 46 | } 47 | 48 | @NonNull 49 | public static App getInstance() { 50 | return mInstance; 51 | } 52 | 53 | @NonNull 54 | public File getRootDir() { 55 | return mRootDir; 56 | } 57 | 58 | private void initRootPath(Context context) { 59 | if (mRootDir != null) return; 60 | 61 | if (FileUtils.storageAvailable()) { 62 | mRootDir = Environment.getExternalStorageDirectory(); 63 | } else { 64 | mRootDir = context.getFilesDir(); 65 | } 66 | mRootDir = new File(mRootDir, "AndServer"); 67 | IOUtils.createFolder(mRootDir); 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/CoreService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm; 17 | 18 | import android.app.Service; 19 | import android.content.Intent; 20 | import android.os.IBinder; 21 | 22 | import com.yanzhenjie.andserver.AndServer; 23 | import com.yanzhenjie.andserver.Server; 24 | import com.sjl.rfm.util.NetUtils; 25 | 26 | import java.util.concurrent.TimeUnit; 27 | 28 | import androidx.annotation.Nullable; 29 | 30 | /** 31 | * Created by Zhenjie Yan on 2018/6/9. 32 | */ 33 | public class CoreService extends Service { 34 | 35 | private Server mServer; 36 | 37 | @Override 38 | public void onCreate() { 39 | mServer = AndServer.serverBuilder(this) 40 | .inetAddress(NetUtils.getLocalIPAddress()) 41 | .port(8090) 42 | .timeout(10, TimeUnit.SECONDS) 43 | .listener(new Server.ServerListener() { 44 | @Override 45 | public void onStarted() { 46 | String hostAddress = mServer.getInetAddress().getHostAddress(); 47 | ServerManager.onServerStart(CoreService.this, hostAddress); 48 | } 49 | 50 | @Override 51 | public void onStopped() { 52 | ServerManager.onServerStop(CoreService.this); 53 | } 54 | 55 | @Override 56 | public void onException(Exception e) { 57 | ServerManager.onServerError(CoreService.this, e.getMessage()); 58 | } 59 | }) 60 | .build(); 61 | } 62 | 63 | @Override 64 | public int onStartCommand(Intent intent, int flags, int startId) { 65 | startServer(); 66 | return START_STICKY; 67 | } 68 | 69 | @Override 70 | public void onDestroy() { 71 | stopServer(); 72 | super.onDestroy(); 73 | } 74 | 75 | /** 76 | * Start server. 77 | */ 78 | private void startServer() { 79 | if (mServer.isRunning()) { 80 | String hostAddress = mServer.getInetAddress().getHostAddress(); 81 | ServerManager.onServerStart(CoreService.this, hostAddress); 82 | } else { 83 | mServer.startup(); 84 | } 85 | } 86 | 87 | /** 88 | * Stop server. 89 | */ 90 | private void stopServer() { 91 | mServer.shutdown(); 92 | } 93 | 94 | @Nullable 95 | @Override 96 | public IBinder onBind(Intent intent) { 97 | return null; 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.rfm; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.sjl.rfm.util.NetUtils; 13 | import com.yanzhenjie.loading.dialog.LoadingDialog; 14 | 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | 18 | import androidx.appcompat.app.AppCompatActivity; 19 | import androidx.appcompat.widget.Toolbar; 20 | 21 | 22 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 23 | 24 | private ServerManager mServerManager; 25 | 26 | private Button mBtnStart; 27 | private Button mBtnStop; 28 | private Button mBtnBrowser; 29 | private TextView mTvMessage; 30 | 31 | private LoadingDialog mDialog; 32 | private String mRootUrl; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | Toolbar toolbar = findViewById(R.id.toolbar); 39 | setSupportActionBar(toolbar); 40 | 41 | mBtnStart = findViewById(R.id.btn_start); 42 | mBtnStop = findViewById(R.id.btn_stop); 43 | mBtnBrowser = findViewById(R.id.btn_browse); 44 | mTvMessage = findViewById(R.id.tv_message); 45 | 46 | mBtnStart.setOnClickListener(this); 47 | mBtnStop.setOnClickListener(this); 48 | mBtnBrowser.setOnClickListener(this); 49 | 50 | // AndServer run in the service. 51 | mServerManager = new ServerManager(this); 52 | mServerManager.register(); 53 | 54 | // startServer; 55 | if (NetUtils.getNetworkAvailableType(this) == 0) { 56 | mBtnStart.performClick(); 57 | } else { 58 | mTvMessage.setText(R.string.no_wlan); 59 | } 60 | 61 | } 62 | 63 | @Override 64 | protected void onDestroy() { 65 | super.onDestroy(); 66 | mServerManager.unRegister(); 67 | mServerManager.stopServer(); 68 | } 69 | 70 | @Override 71 | public void onClick(View v) { 72 | int id = v.getId(); 73 | switch (id) { 74 | case R.id.btn_start: { 75 | if (NetUtils.getNetworkAvailableType(this) != 0) { 76 | Toast.makeText(this, "远程管理需要连接WLAN", Toast.LENGTH_SHORT).show(); 77 | return; 78 | } 79 | showDialog(); 80 | mServerManager.startServer(); 81 | break; 82 | } 83 | case R.id.btn_stop: { 84 | showDialog(); 85 | mServerManager.stopServer(); 86 | break; 87 | } 88 | case R.id.btn_browse: { 89 | if (!TextUtils.isEmpty(mRootUrl)) { 90 | Intent intent = new Intent(); 91 | intent.setAction("android.intent.action.VIEW"); 92 | intent.setData(Uri.parse(mRootUrl)); 93 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 94 | 95 | startActivity(intent); 96 | } 97 | break; 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Start notify. 104 | */ 105 | public void onServerStart(String ip) { 106 | closeDialog(); 107 | mBtnStart.setVisibility(View.GONE); 108 | mBtnStop.setVisibility(View.VISIBLE); 109 | mBtnBrowser.setVisibility(View.VISIBLE); 110 | 111 | if (!TextUtils.isEmpty(ip)) { 112 | List addressList = new LinkedList<>(); 113 | mRootUrl = "http://" + ip + ":8090/"; 114 | addressList.add("当前连接WLAN:" + NetUtils.getConnectWifiSsid(this)); 115 | addressList.add("请在PC端浏览器输入:"); 116 | addressList.add(mRootUrl); 117 | mTvMessage.setText(TextUtils.join("\n", addressList)); 118 | } else { 119 | mRootUrl = null; 120 | mTvMessage.setText(R.string.server_ip_error); 121 | } 122 | } 123 | 124 | /** 125 | * Error notify. 126 | */ 127 | public void onServerError(String message) { 128 | closeDialog(); 129 | mRootUrl = null; 130 | mBtnStart.setVisibility(View.VISIBLE); 131 | mBtnStop.setVisibility(View.GONE); 132 | mBtnBrowser.setVisibility(View.GONE); 133 | mTvMessage.setText(message); 134 | } 135 | 136 | /** 137 | * Stop notify. 138 | */ 139 | public void onServerStop() { 140 | closeDialog(); 141 | mRootUrl = null; 142 | mBtnStart.setVisibility(View.VISIBLE); 143 | mBtnStop.setVisibility(View.GONE); 144 | mBtnBrowser.setVisibility(View.GONE); 145 | mTvMessage.setText(R.string.server_stop_succeed); 146 | } 147 | 148 | private void showDialog() { 149 | if (mDialog == null) mDialog = new LoadingDialog(this); 150 | if (!mDialog.isShowing()) mDialog.show(); 151 | } 152 | 153 | private void closeDialog() { 154 | if (mDialog != null && mDialog.isShowing()) mDialog.dismiss(); 155 | } 156 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/ServerManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm; 17 | 18 | import android.content.BroadcastReceiver; 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.content.IntentFilter; 22 | 23 | /** 24 | * Created by Zhenjie Yan on 2018/6/9. 25 | */ 26 | public class ServerManager extends BroadcastReceiver { 27 | 28 | private static final String ACTION = "com.yanzhenjie.andserver.receiver"; 29 | 30 | private static final String CMD_KEY = "CMD_KEY"; 31 | private static final String MESSAGE_KEY = "MESSAGE_KEY"; 32 | 33 | private static final int CMD_VALUE_START = 1; 34 | private static final int CMD_VALUE_ERROR = 2; 35 | private static final int CMD_VALUE_STOP = 4; 36 | 37 | /** 38 | * Notify serverStart. 39 | * 40 | * @param context context. 41 | */ 42 | public static void onServerStart(Context context, String hostAddress) { 43 | sendBroadcast(context, CMD_VALUE_START, hostAddress); 44 | } 45 | 46 | /** 47 | * Notify serverStop. 48 | * 49 | * @param context context. 50 | */ 51 | public static void onServerError(Context context, String error) { 52 | sendBroadcast(context, CMD_VALUE_ERROR, error); 53 | } 54 | 55 | /** 56 | * Notify serverStop. 57 | * 58 | * @param context context. 59 | */ 60 | public static void onServerStop(Context context) { 61 | sendBroadcast(context, CMD_VALUE_STOP); 62 | } 63 | 64 | private static void sendBroadcast(Context context, int cmd) { 65 | sendBroadcast(context, cmd, null); 66 | } 67 | 68 | private static void sendBroadcast(Context context, int cmd, String message) { 69 | Intent broadcast = new Intent(ACTION); 70 | broadcast.putExtra(CMD_KEY, cmd); 71 | broadcast.putExtra(MESSAGE_KEY, message); 72 | context.sendBroadcast(broadcast); 73 | } 74 | 75 | private MainActivity mActivity; 76 | private Intent mService; 77 | 78 | public ServerManager(MainActivity activity) { 79 | this.mActivity = activity; 80 | mService = new Intent(activity, CoreService.class); 81 | } 82 | 83 | /** 84 | * Register broadcast. 85 | */ 86 | public void register() { 87 | IntentFilter filter = new IntentFilter(ACTION); 88 | mActivity.registerReceiver(this, filter); 89 | } 90 | 91 | /** 92 | * UnRegister broadcast. 93 | */ 94 | public void unRegister() { 95 | mActivity.unregisterReceiver(this); 96 | } 97 | 98 | public void startServer() { 99 | mActivity.startService(mService); 100 | } 101 | 102 | public void stopServer() { 103 | mActivity.stopService(mService); 104 | } 105 | 106 | @Override 107 | public void onReceive(Context context, Intent intent) { 108 | String action = intent.getAction(); 109 | if (ACTION.equals(action)) { 110 | int cmd = intent.getIntExtra(CMD_KEY, 0); 111 | switch (cmd) { 112 | case CMD_VALUE_START: { 113 | String ip = intent.getStringExtra(MESSAGE_KEY); 114 | mActivity.onServerStart(ip); 115 | break; 116 | } 117 | case CMD_VALUE_ERROR: { 118 | String error = intent.getStringExtra(MESSAGE_KEY); 119 | mActivity.onServerError(error); 120 | break; 121 | } 122 | case CMD_VALUE_STOP: { 123 | mActivity.onServerStop(); 124 | break; 125 | } 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/component/AppConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.component; 17 | 18 | import android.content.Context; 19 | 20 | import com.yanzhenjie.andserver.annotation.Config; 21 | import com.yanzhenjie.andserver.framework.config.Multipart; 22 | import com.yanzhenjie.andserver.framework.config.WebConfig; 23 | import com.yanzhenjie.andserver.framework.website.AssetsWebsite; 24 | 25 | import java.io.File; 26 | 27 | /** 28 | * Created by Zhenjie Yan on 2019-06-30. 29 | */ 30 | @Config 31 | public class AppConfig implements WebConfig { 32 | 33 | @Override 34 | public void onConfig(Context context, Delegate delegate) { 35 | delegate.addWebsite(new AssetsWebsite(context, "/httpfilemanager")); 36 | 37 | delegate.setMultipart(Multipart.newBuilder() 38 | .allFileMaxSize(1024 * 1024 * 20) // 20M 39 | .fileMaxSize(1024 * 1024 * 5) // 5M 40 | .maxInMemorySize(1024 * 10) // 1024 * 10 bytes 41 | .uploadTempDir(new File(context.getCacheDir(), "_server_upload_cache_")) 42 | .build()); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/component/AppExceptionResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.component; 17 | 18 | import com.yanzhenjie.andserver.annotation.Resolver; 19 | import com.yanzhenjie.andserver.error.BasicException; 20 | import com.yanzhenjie.andserver.framework.ExceptionResolver; 21 | import com.yanzhenjie.andserver.framework.body.JsonBody; 22 | import com.yanzhenjie.andserver.http.HttpRequest; 23 | import com.yanzhenjie.andserver.http.HttpResponse; 24 | import com.sjl.rfm.util.JsonUtils; 25 | import com.sjl.rfm.util.Logger; 26 | import com.yanzhenjie.andserver.util.StatusCode; 27 | 28 | import androidx.annotation.NonNull; 29 | 30 | /** 31 | * Created by Zhenjie Yan on 2018/9/11. 32 | */ 33 | @Resolver 34 | public class AppExceptionResolver implements ExceptionResolver { 35 | 36 | @Override 37 | public void onResolve(@NonNull HttpRequest request, @NonNull HttpResponse response, @NonNull Throwable e) { 38 | Logger.e("api异常:" + e.getMessage()); 39 | if (e instanceof BasicException) { 40 | BasicException exception = (BasicException) e; 41 | response.setStatus(exception.getStatusCode()); 42 | } else { 43 | response.setStatus(StatusCode.SC_INTERNAL_SERVER_ERROR); 44 | } 45 | String body = JsonUtils.failedJson(response.getStatus(), e.getMessage()); 46 | response.setBody(new JsonBody(body)); 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/component/AppMessageConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.component; 17 | 18 | import com.yanzhenjie.andserver.annotation.Converter; 19 | import com.yanzhenjie.andserver.framework.MessageConverter; 20 | import com.yanzhenjie.andserver.framework.body.JsonBody; 21 | import com.yanzhenjie.andserver.http.ResponseBody; 22 | import com.sjl.rfm.util.JsonUtils; 23 | import com.yanzhenjie.andserver.util.IOUtils; 24 | import com.yanzhenjie.andserver.util.MediaType; 25 | 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.lang.reflect.Type; 29 | import java.nio.charset.Charset; 30 | 31 | import androidx.annotation.NonNull; 32 | import androidx.annotation.Nullable; 33 | 34 | /** 35 | * Created by Zhenjie Yan on 2018/9/11. 36 | */ 37 | @Converter 38 | public class AppMessageConverter implements MessageConverter { 39 | 40 | @Override 41 | public ResponseBody convert(@NonNull Object output, @Nullable MediaType mediaType) { 42 | 43 | return new JsonBody(JsonUtils.successfulJson(output)); 44 | } 45 | 46 | @Nullable 47 | @Override 48 | public T convert(@NonNull InputStream stream, @Nullable MediaType mediaType, Type type) throws IOException { 49 | Charset charset = mediaType == null ? null : mediaType.getCharset(); 50 | if (charset == null) { 51 | return JsonUtils.parseJson(IOUtils.toString(stream), type); 52 | } 53 | return JsonUtils.parseJson(IOUtils.toString(stream, charset), type); 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/component/LoggerInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.component; 17 | 18 | import com.yanzhenjie.andserver.annotation.Interceptor; 19 | import com.yanzhenjie.andserver.framework.HandlerInterceptor; 20 | import com.yanzhenjie.andserver.framework.handler.RequestHandler; 21 | import com.yanzhenjie.andserver.http.HttpMethod; 22 | import com.yanzhenjie.andserver.http.HttpRequest; 23 | import com.yanzhenjie.andserver.http.HttpResponse; 24 | import com.sjl.rfm.util.JsonUtils; 25 | import com.sjl.rfm.util.Logger; 26 | import com.yanzhenjie.andserver.util.MultiValueMap; 27 | 28 | import java.util.List; 29 | 30 | import androidx.annotation.NonNull; 31 | 32 | /** 33 | * 日志拦截器 34 | */ 35 | @Interceptor 36 | public class LoggerInterceptor implements HandlerInterceptor { 37 | 38 | @Override 39 | public boolean onIntercept(@NonNull HttpRequest request, @NonNull HttpResponse response, 40 | @NonNull RequestHandler handler) { 41 | String path = request.getPath(); 42 | List headerNames = response.getHeaderNames(); 43 | HttpMethod method = request.getMethod(); 44 | MultiValueMap valueMap = request.getParameter(); 45 | Logger.i("Path: " + path); 46 | Logger.i("Method: " + method.value()); 47 | Logger.i("Param: " + JsonUtils.toJsonString(valueMap)); 48 | Logger.i("response HeaderNames: " + headerNames); 49 | return false; 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/component/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.component; 17 | 18 | import com.yanzhenjie.andserver.annotation.Interceptor; 19 | import com.yanzhenjie.andserver.error.BasicException; 20 | import com.yanzhenjie.andserver.framework.HandlerInterceptor; 21 | import com.yanzhenjie.andserver.framework.handler.MethodHandler; 22 | import com.yanzhenjie.andserver.framework.handler.RequestHandler; 23 | import com.yanzhenjie.andserver.http.HttpRequest; 24 | import com.yanzhenjie.andserver.http.HttpResponse; 25 | import com.yanzhenjie.andserver.http.session.Session; 26 | import com.yanzhenjie.andserver.mapping.Addition; 27 | 28 | import org.apache.commons.lang3.ArrayUtils; 29 | 30 | import androidx.annotation.NonNull; 31 | 32 | /** 33 | * 登陆拦截器 34 | */ 35 | @Interceptor 36 | public class LoginInterceptor implements HandlerInterceptor { 37 | 38 | public static final String LOGIN_ATTRIBUTE = "USER.LOGIN.SIGN"; 39 | 40 | @Override 41 | public boolean onIntercept(@NonNull HttpRequest request, @NonNull HttpResponse response, 42 | @NonNull RequestHandler handler) { 43 | if (handler instanceof MethodHandler) { 44 | MethodHandler methodHandler = (MethodHandler)handler; 45 | Addition addition = methodHandler.getAddition(); 46 | if (!isLogin(request, addition)) { 47 | throw new BasicException(401, "You are not logged in yet."); 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | private boolean isNeedLogin(Addition addition) { 54 | if (addition == null) return false; 55 | 56 | String[] stringType = addition.getStringType(); 57 | if (ArrayUtils.isEmpty(stringType)) return false; 58 | 59 | boolean[] booleanType = addition.getBooleanType(); 60 | if (ArrayUtils.isEmpty(booleanType)) return false; 61 | return stringType[0].equalsIgnoreCase("login") && booleanType[0]; 62 | } 63 | 64 | private boolean isLogin(HttpRequest request, Addition addition) { 65 | if (isNeedLogin(addition)) { 66 | Session session = request.getSession(); 67 | if (session != null) { 68 | Object o = session.getAttribute(LOGIN_ATTRIBUTE); 69 | return o != null && (o instanceof Boolean) && ((boolean)o); 70 | } 71 | return false; 72 | } 73 | return true; 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/controller/FileManagerController.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sjl.rfm.controller; 3 | 4 | import android.os.Environment; 5 | import android.text.TextUtils; 6 | 7 | import com.yanzhenjie.andserver.annotation.GetMapping; 8 | import com.yanzhenjie.andserver.annotation.PostMapping; 9 | import com.yanzhenjie.andserver.annotation.RequestMapping; 10 | import com.yanzhenjie.andserver.annotation.RequestParam; 11 | import com.yanzhenjie.andserver.annotation.RestController; 12 | import com.yanzhenjie.andserver.framework.body.FileBody; 13 | import com.yanzhenjie.andserver.framework.body.StringBody; 14 | import com.yanzhenjie.andserver.http.HttpRequest; 15 | import com.yanzhenjie.andserver.http.HttpResponse; 16 | import com.yanzhenjie.andserver.http.ResponseBody; 17 | import com.yanzhenjie.andserver.http.cookie.Cookie; 18 | import com.yanzhenjie.andserver.http.session.Session; 19 | import com.sjl.rfm.component.LoginInterceptor; 20 | import com.sjl.rfm.model.FileInfo; 21 | import com.sjl.rfm.util.FileUtils; 22 | import com.sjl.rfm.util.Logger; 23 | import com.sjl.rfm.util.TimeUtils; 24 | import com.yanzhenjie.andserver.util.MediaType; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * 文件管理控制层 32 | * 33 | * @author Kelly 34 | * @version 1.0.0 35 | * @filename SyncActivity.java 36 | * @time 2019/12/19 15:36 37 | * @copyright(C) 2019 song 38 | */ 39 | @RestController 40 | @RequestMapping(path = "/file") 41 | class FileManagerController { 42 | 43 | 44 | @PostMapping(path = "/login", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 45 | String login(HttpRequest request, HttpResponse response, @RequestParam(name = "account") String account, 46 | @RequestParam(name = "password") String password) { 47 | Session session = request.getValidSession(); 48 | session.setAttribute(LoginInterceptor.LOGIN_ATTRIBUTE, true); 49 | 50 | Cookie cookie = new Cookie("account", account + "=" + password); 51 | response.addCookie(cookie); 52 | return "Login successful."; 53 | } 54 | 55 | 56 | /** 57 | * 返回文件列表 58 | * 59 | * @return 60 | */ 61 | @GetMapping(path = "/list") 62 | List getFileList(@RequestParam(name = "rootPath", required = false) String rootPath) { 63 | File file; 64 | if (TextUtils.isEmpty(rootPath)) { 65 | file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()); 66 | } else { 67 | file = new File(rootPath); 68 | } 69 | List fileInfoList = new ArrayList<>(); 70 | if (!file.isDirectory()) { 71 | return fileInfoList;//不能返回空,否则前端收不到数据 72 | } 73 | File[] list = file.listFiles(); 74 | if (list == null || list.length == 0) { 75 | return fileInfoList; 76 | } 77 | FileInfo fileInfo; 78 | for (File f : list) { 79 | String name = f.getName(); 80 | fileInfo = new FileInfo(); 81 | fileInfo.setName(name); 82 | fileInfo.setUrl(f.getAbsolutePath()); 83 | fileInfo.setDateModified(f.lastModified()); 84 | fileInfo.setDateModifiedString(TimeUtils.formatDateToStr(f.lastModified(), "yyyy/MM/dd aHH:mm:ss")); 85 | if (f.isFile()) { 86 | fileInfo.setIsDir(0); 87 | fileInfo.setSize(f.length()); 88 | fileInfo.setSizeString(FileUtils.formatFileSize(f.length())); 89 | } else { 90 | fileInfo.setIsDir(1); 91 | } 92 | fileInfoList.add(fileInfo); 93 | } 94 | return fileInfoList; 95 | } 96 | 97 | 98 | @PostMapping(path = "/download") 99 | ResponseBody download(HttpResponse response, @RequestParam(name = "rootPath", required = false) String rootPath) { 100 | 101 | if (TextUtils.isEmpty(rootPath)) { 102 | return new StringBody("文件路径为空"); 103 | } 104 | try { 105 | File file = new File(rootPath); 106 | if (file.isFile() && file.exists()) { 107 | 108 | FileBody fileBody = new FileBody(file); 109 | response.setStatus(200); 110 | response.setHeader("Accept-Ranges", "bytes"); 111 | response.setHeader("Content-Disposition", "attachment;fileName=" + file.getName()); 112 | return fileBody; 113 | } else { 114 | return new StringBody("文件不存在或已经删除"); 115 | } 116 | } catch (Exception e) { 117 | Logger.e("下载文件异常:" + e.getMessage()); 118 | return new StringBody("下载文件异常:" + e.getMessage()); 119 | } 120 | 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/controller/PageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.controller; 17 | 18 | import com.yanzhenjie.andserver.annotation.Controller; 19 | import com.yanzhenjie.andserver.annotation.GetMapping; 20 | 21 | /** 22 | * Created by Zhenjie Yan on 2018/9/12. 23 | */ 24 | @Controller 25 | public class PageController { 26 | 27 | @GetMapping(path = "/") 28 | public String index() { 29 | // Equivalent to [return "/index"]. 30 | return "forward:/index.html"; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/model/FileInfo.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sjl.rfm.model; 3 | 4 | /** 5 | * 文件信息类 6 | * 7 | * @author Kelly 8 | * @version 1.0.0 9 | * @filename FileInfo.java 10 | * @time 2019/12/19 15:34 11 | * @copyright(C) 2019 song 12 | */ 13 | public class FileInfo { 14 | private String name; 15 | private String url; 16 | /** 17 | * 1目录,0文件 18 | */ 19 | private int isDir; 20 | /** 21 | * 文件大小 22 | */ 23 | private long size; 24 | /** 25 | * 文件大小格式化 26 | */ 27 | private String sizeString; 28 | /** 29 | * 文件修改时间 30 | */ 31 | private long dateModified; 32 | private String dateModifiedString; 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | public String getUrl() { 43 | return url; 44 | } 45 | 46 | public void setUrl(String url) { 47 | this.url = url; 48 | } 49 | 50 | public int getIsDir() { 51 | return isDir; 52 | } 53 | 54 | public void setIsDir(int isDir) { 55 | this.isDir = isDir; 56 | } 57 | 58 | public long getSize() { 59 | return size; 60 | } 61 | 62 | public void setSize(long size) { 63 | this.size = size; 64 | } 65 | 66 | public String getSizeString() { 67 | return sizeString; 68 | } 69 | 70 | public void setSizeString(String sizeString) { 71 | this.sizeString = sizeString; 72 | } 73 | 74 | public long getDateModified() { 75 | return dateModified; 76 | } 77 | 78 | public void setDateModified(long dateModified) { 79 | this.dateModified = dateModified; 80 | } 81 | 82 | public String getDateModifiedString() { 83 | return dateModifiedString; 84 | } 85 | 86 | public void setDateModifiedString(String dateModifiedString) { 87 | this.dateModifiedString = dateModifiedString; 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/model/ReturnData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.model; 17 | 18 | import android.os.Parcel; 19 | import android.os.Parcelable; 20 | 21 | import com.alibaba.fastjson.annotation.JSONField; 22 | 23 | /** 24 | * Created by Zhenjie Yan on 2018/6/9. 25 | */ 26 | public class ReturnData implements Parcelable { 27 | 28 | @JSONField(name = "isSuccess") 29 | private boolean isSuccess; 30 | 31 | @JSONField(name = "errorCode") 32 | private int errorCode; 33 | 34 | @JSONField(name = "errorMsg") 35 | private String errorMsg; 36 | 37 | @JSONField(name = "data") 38 | private Object data; 39 | 40 | public ReturnData() { 41 | } 42 | 43 | protected ReturnData(Parcel in) { 44 | isSuccess = in.readByte() != 0; 45 | errorCode = in.readInt(); 46 | errorMsg = in.readString(); 47 | } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeByte((byte)(isSuccess ? 1 : 0)); 52 | dest.writeInt(errorCode); 53 | dest.writeString(errorMsg); 54 | } 55 | 56 | @Override 57 | public int describeContents() { 58 | return 0; 59 | } 60 | 61 | public static final Creator CREATOR = new Creator() { 62 | @Override 63 | public ReturnData createFromParcel(Parcel in) { 64 | return new ReturnData(in); 65 | } 66 | 67 | @Override 68 | public ReturnData[] newArray(int size) { 69 | return new ReturnData[size]; 70 | } 71 | }; 72 | 73 | public boolean isSuccess() { 74 | return isSuccess; 75 | } 76 | 77 | public void setSuccess(boolean success) { 78 | isSuccess = success; 79 | } 80 | 81 | public int getErrorCode() { 82 | return errorCode; 83 | } 84 | 85 | public void setErrorCode(int errorCode) { 86 | this.errorCode = errorCode; 87 | } 88 | 89 | public String getErrorMsg() { 90 | return errorMsg; 91 | } 92 | 93 | public void setErrorMsg(String errorMsg) { 94 | this.errorMsg = errorMsg; 95 | } 96 | 97 | public Object getData() { 98 | return data; 99 | } 100 | 101 | public void setData(Object data) { 102 | this.data = data; 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.util; 17 | 18 | import android.os.Environment; 19 | import android.webkit.MimeTypeMap; 20 | 21 | import com.yanzhenjie.andserver.http.multipart.MultipartFile; 22 | import com.sjl.rfm.App; 23 | import com.yanzhenjie.andserver.util.StringUtils; 24 | 25 | import java.io.File; 26 | import java.text.DecimalFormat; 27 | import java.util.UUID; 28 | 29 | /** 30 | * Created by Zhenjie Yan on 2018/6/9. 31 | */ 32 | public class FileUtils { 33 | 34 | /** 35 | * Create a random file based on mimeType. 36 | * 37 | * @param file file. 38 | * 39 | * @return file object. 40 | */ 41 | public static File createRandomFile(MultipartFile file) { 42 | String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(file.getContentType().toString()); 43 | if (StringUtils.isEmpty(extension)) { 44 | extension = MimeTypeMap.getFileExtensionFromUrl(file.getFilename()); 45 | } 46 | String uuid = UUID.randomUUID().toString(); 47 | return new File(App.getInstance().getRootDir(), uuid + "." + extension); 48 | } 49 | 50 | /** 51 | * SD is available. 52 | * 53 | * @return true, otherwise is false. 54 | */ 55 | public static boolean storageAvailable() { 56 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 57 | File sd = new File(Environment.getExternalStorageDirectory().getAbsolutePath()); 58 | return sd.canWrite(); 59 | } else { 60 | return false; 61 | } 62 | } 63 | 64 | /** 65 | * 格式化文件大小 66 | * @param size 67 | * @return 68 | */ 69 | public static String formatFileSize(long size) { 70 | if (size <= 0) return "0"; 71 | final String[] units = new String[]{"b", "kb", "M", "G", "T"}; 72 | //计算单位的,原理是利用lg,公式是 lg(1024^n) = nlg(1024),最后 nlg(1024)/lg(1024) = n。 73 | int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); 74 | //计算原理是,size/单位值。单位值指的是:比如说b = 1024,KB = 1024^2 75 | return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.util; 17 | 18 | import com.alibaba.fastjson.JSON; 19 | import com.sjl.rfm.model.ReturnData; 20 | 21 | import java.lang.reflect.Type; 22 | 23 | /** 24 | * Created by Zhenjie Yan on 2018/6/9. 25 | */ 26 | public class JsonUtils { 27 | 28 | /** 29 | * Business is successful. 30 | * 31 | * @param data return data. 32 | * 33 | * @return json. 34 | */ 35 | public static String successfulJson(Object data) { 36 | ReturnData returnData = new ReturnData(); 37 | returnData.setSuccess(true); 38 | returnData.setErrorCode(200); 39 | returnData.setData(data); 40 | return JSON.toJSONString(returnData); 41 | } 42 | 43 | /** 44 | * Business is failed. 45 | * 46 | * @param code error code. 47 | * @param message message. 48 | * 49 | * @return json. 50 | */ 51 | public static String failedJson(int code, String message) { 52 | ReturnData returnData = new ReturnData(); 53 | returnData.setSuccess(false); 54 | returnData.setErrorCode(code); 55 | returnData.setErrorMsg(message); 56 | return JSON.toJSONString(returnData); 57 | } 58 | 59 | /** 60 | * Converter object to json string. 61 | * 62 | * @param data the object. 63 | * 64 | * @return json string. 65 | */ 66 | public static String toJsonString(Object data) { 67 | return JSON.toJSONString(data); 68 | } 69 | 70 | /** 71 | * Parse json to object. 72 | * 73 | * @param json json string. 74 | * @param type the type of object. 75 | * @param type. 76 | * 77 | * @return object. 78 | */ 79 | public static T parseJson(String json, Type type) { 80 | return JSON.parseObject(json, type); 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/util/Logger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.util; 17 | 18 | import android.util.Log; 19 | 20 | /** 21 | * Created by Zhenjie Yan on 2018/9/12. 22 | */ 23 | public class Logger { 24 | 25 | private static final String TAG = "SIMPLE_LOGGER"; 26 | private static final boolean DEBUG = true; 27 | 28 | public static void i(Object obj) { 29 | if (DEBUG) { 30 | Log.i(TAG, obj == null ? "null" : obj.toString()); 31 | } 32 | } 33 | 34 | public static void d(Object obj) { 35 | if (DEBUG) { 36 | Log.d(TAG, obj == null ? "null" : obj.toString()); 37 | } 38 | } 39 | 40 | public static void v(Object obj) { 41 | if (DEBUG) { 42 | Log.v(TAG, obj == null ? "null" : obj.toString()); 43 | } 44 | } 45 | 46 | public static void w(Object obj) { 47 | if (DEBUG) { 48 | Log.w(TAG, obj == null ? "null" : obj.toString()); 49 | } 50 | } 51 | 52 | public static void e(Object obj) { 53 | if (DEBUG) { 54 | Log.e(TAG, obj == null ? "null" : obj.toString()); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/util/NetUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2018 Zhenjie Yan. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sjl.rfm.util; 17 | 18 | import android.content.Context; 19 | import android.net.ConnectivityManager; 20 | import android.net.NetworkInfo; 21 | import android.net.wifi.WifiInfo; 22 | import android.net.wifi.WifiManager; 23 | 24 | import java.net.InetAddress; 25 | import java.net.NetworkInterface; 26 | import java.net.SocketException; 27 | import java.util.Enumeration; 28 | import java.util.regex.Pattern; 29 | 30 | /** 31 | * Created by Zhenjie Yan on 2018/6/9. 32 | */ 33 | public class NetUtils { 34 | 35 | /** 36 | * Ipv4 address check. 37 | */ 38 | private static final Pattern IPV4_PATTERN = Pattern.compile( 39 | "^(" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + 40 | "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); 41 | 42 | /** 43 | * Check if valid IPV4 address. 44 | * 45 | * @param input the address string to check for validity. 46 | * @return True if the input parameter is a valid IPv4 address. 47 | */ 48 | public static boolean isIPv4Address(String input) { 49 | return IPV4_PATTERN.matcher(input).matches(); 50 | } 51 | 52 | /** 53 | * Get local Ip address. 54 | */ 55 | public static InetAddress getLocalIPAddress() { 56 | Enumeration enumeration = null; 57 | try { 58 | enumeration = NetworkInterface.getNetworkInterfaces(); 59 | } catch (SocketException e) { 60 | e.printStackTrace(); 61 | } 62 | if (enumeration != null) { 63 | while (enumeration.hasMoreElements()) { 64 | NetworkInterface nif = enumeration.nextElement(); 65 | Enumeration inetAddresses = nif.getInetAddresses(); 66 | if (inetAddresses != null) { 67 | while (inetAddresses.hasMoreElements()) { 68 | InetAddress inetAddress = inetAddresses.nextElement(); 69 | if (!inetAddress.isLoopbackAddress() && isIPv4Address(inetAddress.getHostAddress())) { 70 | return inetAddress; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | return null; 77 | } 78 | 79 | /** 80 | * 获取当前连接的wifi名称 81 | * @param context 82 | * @return 83 | */ 84 | public static String getConnectWifiSsid(Context context) { 85 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 86 | WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 87 | Logger.i("wifiInfo:" + wifiInfo.toString()); 88 | Logger.i("SSID:" + wifiInfo.getSSID()); 89 | return wifiInfo.getSSID(); 90 | } 91 | 92 | /** 93 | * 0代表连接的是WiFi,1代表连接的是GPRS,2 代表无网络 94 | * 95 | * @return 96 | */ 97 | public static int getNetworkAvailableType(Context context) { 98 | ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 99 | if (manager == null) { 100 | return 2; 101 | } 102 | NetworkInfo networkinfo = manager.getActiveNetworkInfo(); 103 | if (networkinfo == null || !networkinfo.isAvailable()) { 104 | return 2; 105 | } else { 106 | boolean wifi = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting(); 107 | if (wifi) { 108 | return 0; 109 | } else { 110 | return 1; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/rfm/util/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.rfm.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | /** 8 | * TODO 9 | * 10 | * @author Kelly 11 | * @version 1.0.0 12 | * @filename TimeUtils.java 13 | * @time 2019/12/19 17:02 14 | * @copyright(C) 2019 song 15 | */ 16 | public class TimeUtils { 17 | /** 18 | * 将时间转换成日期 19 | * 20 | * @param timeInMillis 21 | * @param dateFormat 22 | * @return 23 | */ 24 | public static String formatDateToStr(long timeInMillis, String dateFormat) { 25 | Calendar calendar = Calendar.getInstance(); 26 | calendar.setTimeInMillis(timeInMillis); 27 | Date date = calendar.getTime(); 28 | return formatDateToStr(date, dateFormat); 29 | } 30 | 31 | /** 32 | * Date转换成字符串日期 33 | * 34 | * @param date 35 | * @param dateFormat 36 | * @return 37 | */ 38 | public static String formatDateToStr(Date date, String dateFormat) { 39 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 40 | return sdf.format(date); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 20 | 21 | 22 | 23 | 30 | 31 | 38 | 39 | 43 | 44 | 49 | 50 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Http文件管理器 4 | 启动服务 5 | 停止服务 6 | 在浏览器中打开 7 | 8 | 服务器停止了 9 | 没有获取到服务器IP地址 10 | 11 | 无WLAN网络 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1dp 4 | 2dp 5 | 3dp 6 | 5dp 7 | 10dp 8 | 15dp 9 | 18dp 10 | 20dp 11 | 25dp 12 | 30dp 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Http File Manager 4 | 5 | Start Server 6 | Stop Server 7 | Open in browser 8 | 9 | The server has stopped. 10 | Did not get the server IP address. 11 | 12 | No WLAN 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "config.gradle" 2 | 3 | buildscript { 4 | 5 | repositories { 6 | jcenter() 7 | google() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.1' 12 | 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | google() 20 | maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'} 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | plugins = [java : 'java', 3 | javaLibrary : 'java-library', 4 | android : 'com.android.application', 5 | androidLibrary: 'com.android.library', 6 | ] 7 | 8 | android = [applicationId : 'com.sjl.rfm', 9 | compileSdkVersion : 28, 10 | buildToolsVersion : '28.0.3', 11 | 12 | libraryMinSdkVersion : 9, 13 | libraryTargetSdkVersion: 28, 14 | sampleMinSdkVersion : 14, 15 | sampleTargetSdkVersion : 22, 16 | 17 | versionCode : 22, 18 | versionName : '2.0.5',] 19 | 20 | 21 | 22 | dependencies = [ 23 | commonsLang : 'org.apache.commons:commons-lang3:3.8', 24 | commonsCollections: 'org.apache.commons:commons-collections4:4.3', 25 | 26 | appCompat : 'androidx.appcompat:appcompat:1.0.2', 27 | design : 'com.google.android.material:material:1.0.0', 28 | 29 | loading : 'com.yanzhenjie:loading:1.0.0', 30 | json : 'com.alibaba:fastjson:1.1.70.android'] 31 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/gradle.properties -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshot/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/screenshot/example1.png -------------------------------------------------------------------------------- /screenshot/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/RemoteFileManager/027f37bc451809a7a531c04cf25f7476e43cd1c1/screenshot/example2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' --------------------------------------------------------------------------------