初次提交

master
gtf35 6 years ago
commit e43f46bdf8
  1. 10
      .gitignore
  2. BIN
      .idea/caches/build_file_checksums.ser
  3. 29
      .idea/codeStyles/Project.xml
  4. 18
      .idea/gradle.xml
  5. 83
      .idea/markdown-navigator.xml
  6. 3
      .idea/markdown-navigator/profiles_settings.xml
  7. 29
      .idea/misc.xml
  8. 7
      .idea/vcs.xml
  9. 21
      LICENSE
  10. 157
      README.md
  11. 1
      app/.gitignore
  12. 28
      app/build.gradle
  13. 21
      app/proguard-rules.pro
  14. 26
      app/src/androidTest/java/top/gtf35/shellapplicatontest/ExampleInstrumentedTest.java
  15. 23
      app/src/main/AndroidManifest.xml
  16. 10
      app/src/main/java/shellService/Main.java
  17. 79
      app/src/main/java/shellService/Service.java
  18. 129
      app/src/main/java/shellService/ServiceShellUtils.java
  19. 28
      app/src/main/java/shellService/ServiceThread.java
  20. 69
      app/src/main/java/top/gtf35/shellapplicatontest/MainActivity.java
  21. 99
      app/src/main/java/top/gtf35/shellapplicatontest/SocketClient.java
  22. 34
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  23. 170
      app/src/main/res/drawable/ic_launcher_background.xml
  24. 46
      app/src/main/res/layout/activity_main.xml
  25. 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  26. 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  27. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  28. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  29. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  30. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  31. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  32. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  33. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  34. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  35. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  36. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  37. 6
      app/src/main/res/values/colors.xml
  38. 3
      app/src/main/res/values/strings.xml
  39. 11
      app/src/main/res/values/styles.xml
  40. 17
      app/src/test/java/top/gtf35/shellapplicatontest/ExampleUnitTest.java
  41. 27
      build.gradle
  42. 13
      gradle.properties
  43. BIN
      gradle/wrapper/gradle-wrapper.jar
  44. 6
      gradle/wrapper/gradle-wrapper.properties
  45. 172
      gradlew
  46. 84
      gradlew.bat
  47. 1
      settings.gradle

10
.gitignore vendored

@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

@ -0,0 +1,29 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownProjectSettings" wasCopied="true">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.25" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="true" />
<option name="DEFINITIONS" value="false" />
<option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" />
<option name="HARDWRAPS" value="false" />
<option name="HTML_DEEP_PARSER" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="false" />
</PegdownExtensions>
<ParserOptions>
<option name="ADMONITION_EXT" value="false" />
<option name="ATTRIBUTES_EXT" value="false" />
<option name="COMMONMARK_LISTS" value="true" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="ENUMERATED_REFERENCES_EXT" value="false" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_LISTS" value="false" />
<option name="GITHUB_WIKI_LINKS" value="false" />
<option name="GITLAB_EXT" value="false" />
<option name="GITLAB_MATH_EXT" value="false" />
<option name="GITLAB_MERMAID_EXT" value="false" />
<option name="HEADER_ID_NON_ASCII_TO_LOWERCASE" value="false" />
<option name="HEADER_ID_NO_DUPED_DASHES" value="false" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="MACROS_EXT" value="false" />
<option name="NO_TEXT_ATTRIBUTES" value="false" />
<option name="PARSE_HTML_ANCHOR_ID" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false">
<GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="false" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>

@ -0,0 +1,3 @@
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" />
</component>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 gtf35
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,157 @@
# 免Root实现静默安装和点击任意位置
## 0 前言
最近有了个需求:免 root 实现任意位置点击和静默安装。这个做过的小伙伴应该都知道正常情况下是不可能实现的。无障碍只能实现对已知控件的点击,并不能指定坐标。但是确实有人另辟蹊径做出来了,譬如做游戏手柄的飞智,他们是用一个激活器,手机开 usb 调试,然后插在激活器上并授权,飞智游戏厅就被「激活」了,然后可以实现任意位置点击。如果不了解的可以去他们官网了解下,在这里不多赘述了。无独有偶,黑域也使用了类似的手段,也可以用电脑的usb调试激活。我们知道,任意位置坐标xy点击是可以在 pc 上通过 shell 命令「input tap x y」来实现的,也不需要 root 权限。但是在应用内通过「Runtime.getRuntime().exec」执行这个 shell 命令却提示「permission denied」也就是权限不足。但是飞智或者黑域却好像使用了某种魔法,提升了自己的权限,那么问题来了:如何用 usb 调试给 app 提权?
## 1 原理揭晓
「如何用 usb 调试给 app 提权」这个问题乍一看确实没问题,但是知乎有个回答是「先问是不是,再问为什么」我觉得说的很好。我被这个问题给困扰了很久,最后发现我问错了。先放出结论「并不是给 app 提权,而是运行了一个由设立了权限的新程序」
刚才的问题先放一边,我来问大家个新问题,怎样让 app 获取 root 权限?这个问题答案已经有不少了,网上一查便可知其实是获取「Runtime.getRuntime().exec」的流,在里面用su提权,然后就可以执行需要 root 权限的 shell 命令,比如挂载 system 读写,访问 data 分区,用 shell 命令静默安装,等等。话说回来,是不是和我们今天的主题有点像,如何使 app 获取 shell 权限?嗯,其实差不多,思路也类似,因为本来 root 啦, shell 啦,根本就不是 Android 应用层的名词呀,他们本来就是 Linux 里的名词,只不过是 Android 框架运行于 Linux 层之上, 我们可以调用 shell 命令,也可以在shell 里调用 su 来使shell 获取 root 权限,来绕过 Android 层做一些被限制的事。然而在 app 里调用 shell 命令,其进程还是 app 的,权限还是受限。所以就不能在 app 里运行 shell 命令,那么问题来了,不在 app 里运行在哪运行?答案是在 pc 上运行。当然不可能是 pc 一直连着手机啦,而是 pc 上在 shell 里运行独立的一个 java 程序,这个程序因为是在 shell 里启动的,所以具有 shell 权限。我们想一下,这个 Java 程序在 shell 里运行,建立本地 socket 服务器,和 app 通信,远程执行 app 下发的代码。因为即使拔掉了数据线,这个 Java 程序也不会停止,只要不重启他就一直活着,执行我们的命令,这不就是看起来 app 有了 shell 权限?现在真相大白,飞智和黑域用 usb 调试激活的那一下,其实是启动那个 Java 程序,飞智是执行模拟按键,黑域是监听系统事件,你想干啥就任你开发了。「注:黑域和飞智由于进程管理的需要,其实是先用 shell 启动一个 so ,然后再用 so 做跳板启动 Java 程序,而且 so 也充当守护进程,当 Java 意外停止可以重新启动,读着有兴趣可以自行研究,在此不多做说明」
## 2 好耶!是 app_process
那么如何具体用 shell 运行 Java 程序呢?肯定不是「java xxx.jar」啦,Android 能运行的格式是 dex 。没错,就是apk 里那个 dex 。然后我们可以通过「app_process」开启动 Java 。app_process 的参数如下
```shell
app_process [vm-options] cmd-dir [options] start-class-name [main-options]
```
这个诡异又可怕的东西是没有 -help 的。我们要么看源码,要么看别人分析好的。本人水平有限,这里选择看别人分析好的:
```shell
vm-options – VM 选项
cmd-dir –父目录 (/system/bin)
options –运行的参数 :
–zygote
–start-system-server
–application (api>=14)
–nice-name=nice_proc_name (api>=14)
start-class-name –包含main方法的主类 (com.android.commands.am.Am)
main-options –启动时候传递到main方法中的参数
```
## 3 实践
因为是 dex 我们就直接在 as 里写吧,提取 dex 也方便。新建个空白项目,初始结构是这样:
![](http://article.gtf35.top/app_process/as%E9%BB%98%E8%AE%A4%E6%A6%82%E8%A7%88.JPG)
我们新建个包,存放我们要在 shell 下运行的 Java 代码:
![](http://article.gtf35.top/app_process/%E7%AC%AC%E4%B8%80%E6%AC%A1%E6%B5%8B%E8%AF%95.JPG)
这里我们补全 Main 方法,因为我们这个不是个 Android 程序,只是编译成 dex 的纯 Java 程序,所以我们这个的入口是 Main :
```java
package shellService;
public class Main {
public static void main(String[] args){
System.out.println("我是在 shell 里运行的!!!");
}
}
```
我们在代码里只是打印一行「我是在 shell 里运行的!!!」,因为这里是纯 Java 所以也用的 println。现在编译 apk:
![](http://article.gtf35.top/app_process/%E7%BC%96%E8%AF%91%E5%87%BA%E6%9D%A5%E7%9A%84apk.JPG)
因为 apk 就是 zip 所以我们直接解压出 apk 文件,然后执行 :
```shell
adb push classes.dex /data/local/tmp
cd /data/local/tmp
app_process -Djava.class.path=/data/local/tmp/classes.dex /system/bin shellService.Main
```
这时就能看到已经成功运行啦:
![](http://article.gtf35.top/app_process/%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.JPG)
这里因为 utf8 在 Windows shell 里有问题,所以乱码了,但是还是说明我们成功了。
##4 具有实用性
只能输出肯定是不行的,不具有实用性。我们之前说过,我们应该建立个本地 socket 服务器来接受命令并执行,这里的「Service」类实现了这个功能,因为如何建立 socket 不是文章的重点,所以大家只要知道这个类内部实现了一个「ServiceGetText」接口,在收到命令之后会把命令内容作为参数回掉 getText 方法,然后我们执行 shell 命令之后,吧结果作为字符串返回即可,具体实现可以看文末的 GitHub。
我们新建一个「ServiceThread」来运行「Service」服务和执行设立了命令:
```java
public class ServiceThread extends Thread {
private static int ShellPORT = 4521;
@Override
public void run() {
System.out.println(">>>>>>Shell服务端程序被调用<<<<<<");
new Service(new Service.ServiceGetText() {
@Override
public String getText(String text) {
if (text.startsWith("###AreYouOK")){
return "###IamOK#";
}
try{
ServiceShellUtils.ServiceShellCommandResult sr = ServiceShellUtils.execCommand(text, false);
if (sr.result == 0){
return "###ShellOK#" + sr.successMsg;
} else {
return "###ShellError#" + sr.errorMsg;
}
}catch (Exception e){
return "###CodeError#" + e.toString();
}
}
}, ShellPORT);
}
}
```
其中 ServiceShellUtils 用到了开源项目 ShellUtils 在此感谢。这个类用来执行 shell 命令。
然后在 Main 中调用这个线程:
```java
public class Main {
public static void main(String[] args){
new ServiceThread().start();
while (true);
}
}
```
这样,我们服务端就准备好了,我们来写控制服务端的 app 。我们新建类「SocketClient」用来和服务端进行通信,并在活动里调用他:
```java
```
然后重复 3 小节的操作,运行这个服务端:
![](http://article.gtf35.top/app_process/%E6%9C%80%E5%90%8Edemo%E7%9A%84shell.JPG)
然后安装 apk ,运行:
```java
```
![](http://article.gtf35.top/app_process/%E6%89%8B%E6%9C%BA%E8%BF%90%E8%A1%8C.gif)
可以看到,在不 root 的情况下,成功的执行了需要 shell 权限的命令
##5 最可爱的人
最后,我真的是要由衷的感谢各种技术分析文章和开源项目,真的太感谢了,没有无条件的奉献就没有互联网这么快的进步。
我对 app_process 利用方法的研究离不开以下项目和前辈的汗水:
[Brevent](https://github.com/brevent/Brevent) 最早利用app_process进程实现无 root 权限使用的开源应用(虽然已经闭源,仍然尊重并感谢 [liudongmiao](https://github.com/liudongmiao))
[Android system log viewer on Android phone without root.](https://github.com/Zane96/Fairy) 利用app_process进程实现无 root 权限使用的优秀开源应用
[Android上app_process启动java进程](https://blog.csdn.net/u010651541/article/details/53163542) 通俗易懂的教程
[使用 app_process 来调用高权限 API](https://haruue.moe/blog/2017/08/30/call-privileged-api-with-app-process/) 分析的很深刻的教程

1
app/.gitignore vendored

@ -0,0 +1 @@
/build

@ -0,0 +1,28 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "top.gtf35.shellapplicatontest"
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.+'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,26 @@
package top.gtf35.shellapplicatontest;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("top.gtf35.shellapplicatontest", appContext.getPackageName());
}
}

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="top.gtf35.shellapplicatontest">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,10 @@
package shellService;
public class Main {
public static void main(String[] args){
new ServiceThread().start();
while (true);
}
}

@ -0,0 +1,79 @@
package shellService;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Service {
private ServiceGetText mServiceGetText;
public Service(ServiceGetText serviceGetText, int PORT) {
mServiceGetText = serviceGetText;
try {
/** 创建ServerSocket*/
// 创建一个ServerSocket在端口4521监听客户请求
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("服务端运行在" + PORT + "端口");
while (true) {
// 侦听并接受到此Socket的连接,请求到来则产生一个Socket对象,并继续执行
Socket socket = serverSocket.accept();
System.out.println("监听请求到来则产生一个Socket对象,并继续执行");
new CreateServerThread(socket);//当有请求时,启一个线程处理
}
} catch (Exception e) {
System.out.println("连接监听发生错误 Exception:" + e);
}finally{
// serverSocket.close();
}
}
//线程类
class CreateServerThread extends Thread {
Socket socket;
public CreateServerThread(Socket s) throws IOException {
System.out.println("创建了一个新的连接线程");
socket = s;
start();
}
public void run() {
try {
/** 获取客户端传来的信息 */
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("由Socket对象得到输入流,并构造相应的BufferedReader对象");
String line = bufferedReader.readLine();
/** 发送服务端准备传输的 */
// 由Socket对象得到输出流,并构造PrintWriter对象
PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
System.out.println("由Socket对象得到输出流,并构造PrintWriter对象");
// 获取从客户端读入的字符串
System.out.println("while循环:获取从客户端读入的字符串");
System.out.println("while循环:客户端返回 : " + line);
String repeat = mServiceGetText.getText(line);
System.out.println("while循环:服务器将返回:" + repeat);
//printWriter.print("hello Client, I am Server!");
printWriter.print(repeat);
System.out.println("while循环:准备刷新返回");
printWriter.flush();
System.out.println("while循环:已刷新返回");
System.out.println("关闭Socket");
/** 关闭Socket*/
printWriter.close();
bufferedReader.close();
socket.close();
} catch (IOException e) {
System.out.println("socket 连接线程发生错误:" + e.toString());
}
}
}
public interface ServiceGetText{
String getText(String text);
}
}

@ -0,0 +1,129 @@
package shellService;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
public class ServiceShellUtils {
public static final String COMMAND_SU = "su"; // 获取root权限的命令
public static final String COMMAND_SH = "sh"; // 执行sh文件的命令
public static final String COMMAND_EXIT = "exit\n"; // 退出的命令
public static final String COMMAND_LINE_END = "\n"; // 执行命令必须加在末尾
private ServiceShellUtils() {
throw new AssertionError();
}
//检测root状态
public static boolean checkRootPermission() {
return execCommand("echo root", true, false).result == 0;
}
//执行单行命令,实际还是调用的执行多行 ,传入命令和是否需要root
public static ServiceShellUtils.ServiceShellCommandResult execCommand(String command, boolean isRoot) {
return execCommand(new String[]{command}, isRoot, true);
}
//执行List<String>中的命令 , 传入List和是否需要root
public static ServiceShellUtils.ServiceShellCommandResult execCommand(List<String> commands, boolean isRoot) {
return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, true);
}
//执行多行命令
public static ServiceShellUtils.ServiceShellCommandResult execCommand(String[] commands, boolean isRoot) {
return execCommand(commands, isRoot, true);
}
public static ServiceShellUtils.ServiceShellCommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(new String[]{command}, isRoot, isNeedResultMsg);
}
public static ServiceShellUtils.ServiceShellCommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg);
}
//执行命令,获得返回的信息
public static ServiceShellUtils.ServiceShellCommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
int result = -1;
if (commands == null || commands.length == 0) {
return new ServiceShellUtils.ServiceShellCommandResult(result, null, null);
}
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) {
continue;
}
os.write(command.getBytes());
os.writeBytes(COMMAND_LINE_END);
os.flush();
}
os.writeBytes(COMMAND_EXIT);
os.flush();
result = process.waitFor();
// get command result
if (isNeedResultMsg) {
successMsg = new StringBuilder();
errorMsg = new StringBuilder();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
return new ServiceShellUtils.ServiceShellCommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
: errorMsg.toString());
}
//封装了返回信息
public static class ServiceShellCommandResult {
public int result;
public String successMsg; //成功信息
public String errorMsg; // 错误信息
public ServiceShellCommandResult(int result) {
this.result = result;
}
public ServiceShellCommandResult(int result, String successMsg, String errorMsg) {
this.result = result;
this.successMsg = successMsg;
this.errorMsg = errorMsg;
}
}
}

@ -0,0 +1,28 @@
package shellService;
public class ServiceThread extends Thread {
private static int ShellPORT = 4521;
@Override
public void run() {
System.out.println(">>>>>>Shell服务端程序被调用<<<<<<");
new Service(new Service.ServiceGetText() {
@Override
public String getText(String text) {
if (text.startsWith("###AreYouOK")){
return "###IamOK#";
}
try{
ServiceShellUtils.ServiceShellCommandResult sr = ServiceShellUtils.execCommand(text, false);
if (sr.result == 0){
return "###ShellOK#" + sr.successMsg;
} else {
return "###ShellError#" + sr.errorMsg;
}
}catch (Exception e){
return "###CodeError#" + e.toString();
}
}
}, ShellPORT);
}
}

@ -0,0 +1,69 @@
package top.gtf35.shellapplicatontest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText mCmdInputEt;
private Button mRunShellBtn;
private TextView mOutputTv;
private void initView(){
mCmdInputEt = findViewById(R.id.et_cmd);
mRunShellBtn = findViewById(R.id.btn_runshell);
mOutputTv = findViewById(R.id.tv_output);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mRunShellBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String cmd = mCmdInputEt.getText().toString();
if (TextUtils.isEmpty(cmd)) {
Toast.makeText(MainActivity.this, "输入内容为空", Toast.LENGTH_SHORT).show();
return;
}
runShell(cmd);
}
});
}
private void runShell(final String cmd){
if (TextUtils.isEmpty(cmd)) return;
new Thread(new Runnable() {
@Override
public void run() {
new SocketClient(cmd, new SocketClient.onServiceSend() {
@Override
public void getSend(String result) {
showTextOnTextView(result);
}
});
}
}).start();
}
private void showTextOnTextView(final String text){
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if (TextUtils.isEmpty(mOutputTv.getText())) {
mOutputTv.setText(text);
} else {
mOutputTv.setText(mOutputTv.getText() + "\n" + text);
}
}
});
}
}

@ -0,0 +1,99 @@
package top.gtf35.shellapplicatontest;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
public class SocketClient {
private String TAG = "SocketClient";
private String HOST = "127.0.0.1";
PrintWriter printWriter;//发送用的
onServiceSend mOnServiceSend;
String cmd;
BufferedReader bufferedReader;
int port = 4521;
public SocketClient(String commod, onServiceSend onServiceSend) {
cmd = commod;
mOnServiceSend = onServiceSend;
try {
Log.d(TAG, "与service进行socket通讯,地址=" + HOST + ":" + port);
/** 创建Socket*/
// 创建一个流套接字并将其连接到指定 IP 地址的指定端口号(本处是本机)
Socket socket = new Socket();
socket.connect(new InetSocketAddress(HOST, port), 3000);//设置连接请求超时时间3 s
// 接收3s超时
socket.setSoTimeout(3000);
Log.d(TAG, "与service进行socket通讯,超时为:" + 3000);
/** 发送客户端准备传输的信息 */
// 由Socket对象得到输出流,并构造PrintWriter对象
printWriter = new PrintWriter(socket.getOutputStream(), true);
/** 用于获取服务端传输来的信息 */
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
new CreateServerThread(socket);
send(cmd);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "与service进行socket通讯发生错误" + e);
mOnServiceSend.getSend("###ShellRunError:" + e.toString());
}
}
//线程类
class CreateServerThread extends Thread {
Socket socket;
InputStreamReader inputStreamReader;
BufferedReader reader;
public CreateServerThread(Socket s) throws IOException {
Log.d(TAG, "创建了一个新的连接线程");
socket = s;
start();
}
public void run() {
try {
// 打印读入一字符串并回调
try {
inputStreamReader = new InputStreamReader(socket.getInputStream());
reader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = reader.readLine()) != null) {
if (line != null)mOnServiceSend.getSend(line);
}
Log.d(TAG, "客户端接收解析服务器的while循环结束");
} catch (Exception e){
e.printStackTrace();
Log.d(TAG, "客户端接收解析服务器的Threadcatch块执行:" + e.toString());
}
bufferedReader.close();
printWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "socket 接收线程发生错误:" + e.toString());
}
}
}
public void send(String cmd){
printWriter.println(cmd );
// 刷新输出流,使Server马上收到该字符串
printWriter.flush();
}
public interface onServiceSend{
void getSend(String result);
}
}

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_cmd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="在此处输入shell命令"
android:inputType="none"
app:layout_constraintEnd_toStartOf="@+id/btn_runshell"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_runshell"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="执行"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_output"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_cmd" />
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

@ -0,0 +1,3 @@
<resources>
<string name="app_name">ShellApplicatonTest</string>
</resources>

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

@ -0,0 +1,17 @@
package top.gtf35.shellapplicatontest;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

@ -0,0 +1,13 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Binary file not shown.

@ -0,0 +1,6 @@
#Sat Feb 23 22:41:16 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

172
gradlew vendored

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1 @@
include ':app'
Loading…
Cancel
Save