├── .gitignore ├── JitPackUpload.gradle ├── README.md ├── apk └── xfloatview_demo_1.0.apk ├── app ├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xuexiang │ │ └── xfloatview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xuexiang │ │ │ └── xfloatviewdemo │ │ │ ├── App.java │ │ │ ├── activity │ │ │ └── MainActivity.java │ │ │ ├── adapter │ │ │ ├── AppTrafficAdapter.java │ │ │ ├── BaseRecyclerAdapter.java │ │ │ └── RecyclerViewHolder.java │ │ │ ├── entity │ │ │ └── AppTrafficInfo.java │ │ │ ├── fragment │ │ │ ├── AppTrafficFragment.java │ │ │ ├── MainFragment.java │ │ │ ├── NetWorkMonitorFragment.java │ │ │ └── SmartViewFragment.java │ │ │ ├── service │ │ │ ├── NetWorkMonitor.java │ │ │ ├── NetWorkMonitorService.java │ │ │ └── SmartViewService.java │ │ │ ├── util │ │ │ └── SystemKeyboard.java │ │ │ └── widget │ │ │ ├── MonitorView.java │ │ │ └── smart │ │ │ ├── SmartIcon.java │ │ │ ├── SmartPanel.java │ │ │ └── SmartView.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_back_click.png │ │ ├── ic_back_normal.png │ │ ├── ic_close_click.png │ │ ├── ic_close_normal.png │ │ ├── ic_home_click.png │ │ ├── ic_home_normal.png │ │ ├── ic_menu_click.png │ │ ├── ic_menu_normal.png │ │ ├── ic_more_click.png │ │ ├── ic_more_normal.png │ │ ├── ic_recent_click.png │ │ ├── ic_recent_normal.png │ │ ├── ic_show.png │ │ ├── ic_show_click.png │ │ ├── ic_show_normal.png │ │ ├── ic_voicedown_click.png │ │ ├── ic_voicedown_normal.png │ │ ├── ic_voiceup_click.png │ │ └── ic_voiceup_normal.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_smart_panel.xml │ │ ├── ic_launcher_background.xml │ │ ├── selector_ic_back.xml │ │ ├── selector_ic_close.xml │ │ ├── selector_ic_home.xml │ │ ├── selector_ic_menu.xml │ │ ├── selector_ic_more.xml │ │ ├── selector_ic_recent.xml │ │ ├── selector_ic_voice_down.xml │ │ └── selector_ic_voice_up.xml │ │ ├── layout │ │ ├── activity_app_traffic.xml │ │ ├── adapter_recycler_app_traffic_item.xml │ │ ├── layout_monitor.xml │ │ ├── layout_smarticon.xml │ │ └── layout_smartpanel.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xuexiang │ └── xfloatview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── demo.gif └── download.png ├── settings.gradle ├── versions.gradle └── xfloatview-lib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml └── java └── com └── xuexiang └── xfloatview ├── Utils.java ├── XFloatView.java └── permission ├── FloatWindowPermission.java ├── IPermissionApplyPrompter.java └── rom ├── HuaweiUtils.java ├── MeizuUtils.java ├── MiuiUtils.java ├── OppoUtils.java ├── QikuUtils.java └── RomUtils.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /keystores 4 | /local.properties 5 | /.idea 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /JitPackUpload.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | // 指定group,com.github.<用户名>,这里我默认填写的是我的github账号,请换成你自己的。 4 | group='com.github.xuexiangjys' 5 | 6 | //--------------------------------------------- 7 | 8 | 9 | // 指定编码 10 | tasks.withType(JavaCompile) { 11 | options.encoding = "UTF-8" 12 | } 13 | 14 | tasks.withType(Javadoc) { 15 | options.encoding = 'UTF-8' 16 | } 17 | 18 | if (project.hasProperty("android")) { // Android libraries 19 | task sourcesJar(type: Jar) { 20 | classifier = 'sources' 21 | from android.sourceSets.main.java.srcDirs 22 | } 23 | 24 | task javadoc(type: Javadoc) { 25 | failOnError false 26 | source = android.sourceSets.main.java.srcDirs 27 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 28 | } 29 | } else { // Java libraries 30 | task sourcesJar(type: Jar, dependsOn: classes) { 31 | classifier = 'sources' 32 | from sourceSets.main.allSource 33 | } 34 | } 35 | 36 | javadoc { 37 | options { 38 | encoding "UTF-8" 39 | charSet 'UTF-8' 40 | author true 41 | version true 42 | links "http://docs.oracle.com/javase/7/docs/api" 43 | } 44 | } 45 | 46 | // 制作文档(Javadoc) 47 | task javadocJar(type: Jar, dependsOn: javadoc) { 48 | classifier = 'javadoc' 49 | from javadoc.destinationDir 50 | } 51 | 52 | artifacts { 53 | archives javadocJar 54 | archives sourcesJar 55 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XFloatView 2 | [![xfv][xfvsvg]][xfv] [![api][apisvg]][api] 3 | 4 | 一个简易的悬浮窗实现方案 5 | 6 | ## 关于我 7 | 8 | [![github](https://img.shields.io/badge/GitHub-xuexiangjys-blue.svg)](https://github.com/xuexiangjys) [![csdn](https://img.shields.io/badge/CSDN-xuexiangjys-green.svg)](http://blog.csdn.net/xuexiangjys) 9 | 10 | ## 特征 11 | 12 | * 支持自定义布局的悬浮窗。 13 | 14 | * 支持自定义拖动事件、点击事件。 15 | 16 | * 支持悬浮窗自动吸附效果。 17 | 18 | * 支持初始化悬浮窗的位置。 19 | 20 | * 支持悬浮窗翻转吸附。 21 | 22 | -------- 23 | 24 | ## 1、演示(请star支持) 25 | 26 | ### 1.1、Demo演示动画 27 | 28 | ![][demo-gif] 29 | 30 | ### 1.2、Demo下载 31 | 32 | [![downloads][download-svg]][download-url] 33 | 34 | ![][download-img] 35 | 36 | ## 2、如何使用 37 | 38 | 目前支持主流开发工具AndroidStudio的使用,直接配置build.gradle,增加依赖即可. 39 | 40 | ### 2.1、Android Studio导入方法,添加Gradle依赖 41 | 42 | 1.先在项目根目录的 build.gradle 的 repositories 添加: 43 | 44 | ``` 45 | allprojects { 46 | repositories { 47 | ... 48 | maven { url "https://jitpack.io" } 49 | } 50 | } 51 | ``` 52 | 53 | 2.然后在dependencies添加: 54 | 55 | ``` 56 | dependencies { 57 | ... 58 | implementation 'com.github.xuexiangjys:XFloatView:1.0.1' 59 | } 60 | ``` 61 | 62 | ### 2.2、继承XFloatView,实现自定义窗体 63 | 64 | 主要需要实现如下抽象方法: 65 | 66 | ``` 67 | /** 68 | * @return 获取根布局的ID 69 | */ 70 | protected abstract int getLayoutId(); 71 | 72 | /** 73 | * @return 能否移动或者触摸响应 74 | */ 75 | protected abstract boolean canMoveOrTouch(); 76 | 77 | /** 78 | * 初始化悬浮控件 79 | */ 80 | protected abstract void initFloatView(); 81 | 82 | /** 83 | * 初始化监听 84 | */ 85 | protected abstract void initListener(); 86 | 87 | /** 88 | * @return 设置悬浮框是否吸附在屏幕边缘 89 | */ 90 | protected abstract boolean isAdsorbView(); 91 | ``` 92 | 93 | [点击查看示例代码](https://github.com/xuexiangjys/XFloatView/tree/master/app/src/main/java/com/xuexiang/xfloatviewdemo/widget) 94 | 95 | ### 2.3、悬浮窗的权限申请 96 | 97 | ``` 98 | FloatWindowPermission.getInstance().applyFloatWindowPermission(getContext()); 99 | ``` 100 | 101 | ## 联系方式 102 | 103 | [![](https://img.shields.io/badge/点击一键加入QQ交流群-602082750-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=9922861ef85c19f1575aecea0e8680f60d9386080a97ed310c971ae074998887) 104 | 105 | ![](https://github.com/xuexiangjys/XPage/blob/master/img/qq_group.jpg) 106 | 107 | 108 | [xfvsvg]: https://img.shields.io/badge/XFloatView-v1.0.1-brightgreen.svg 109 | [xfv]: https://github.com/xuexiangjys/XFloatView 110 | [apisvg]: https://img.shields.io/badge/API-14+-brightgreen.svg 111 | [api]: https://android-arsenal.com/api?level=14 112 | 113 | 114 | [demo-gif]: https://github.com/xuexiangjys/XFloatView/blob/master/img/demo.gif 115 | [download-svg]: https://img.shields.io/badge/downloads-1.5M-blue.svg 116 | [download-url]: https://github.com/xuexiangjys/XFloatView/blob/master/apk/xfloatview_demo_1.0.apk?raw=true 117 | [download-img]: https://github.com/xuexiangjys/XFloatView/blob/master/img/download.png -------------------------------------------------------------------------------- /apk/xfloatview_demo_1.0.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/apk/xfloatview_demo_1.0.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | ## 按钮救星 2 | 3 | 1.开启按钮救星 4 | 5 | ``` 6 | adb shell am startservice -n com.xuexiang.xfloatviewdemo/com.xuexiang.xfloatviewdemo.service.SmartViewService 7 | ``` 8 | 9 | 2.关闭按钮救星 10 | 11 | ``` 12 | adb shell am stopservice -n com.xuexiang.xfloatviewdemo/com.xuexiang.xfloatviewdemo.service.SmartViewService 13 | ``` -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion build_versions.target_sdk 5 | buildToolsVersion build_versions.build_tools 6 | 7 | defaultConfig { 8 | applicationId "com.xuexiang.xfloatviewdemo" 9 | minSdkVersion 14 10 | targetSdkVersion build_versions.target_sdk 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | if (isNeedPackage.toBoolean()) { 17 | signingConfigs { 18 | release { 19 | storeFile file(app_release.storeFile) 20 | storePassword app_release.storePassword 21 | keyAlias app_release.keyAlias 22 | keyPassword app_release.keyPassword 23 | } 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled true 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | if (isNeedPackage.toBoolean()) { 32 | signingConfig signingConfigs.release 33 | } 34 | } 35 | } 36 | 37 | if (isNeedPackage.toBoolean()) { 38 | applicationVariants.all { variant -> 39 | variant.outputs.all { 40 | if (variant.buildType.name.equals('release')) { 41 | outputFileName = "xfloatview_demo_${defaultConfig.versionName}.apk" 42 | } 43 | } 44 | } 45 | } 46 | 47 | lintOptions { 48 | abortOnError false 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation fileTree(include: ['*.jar'], dir: 'libs') 54 | implementation deps.support.app_compat 55 | testImplementation deps.junit 56 | androidTestImplementation deps.runner 57 | androidTestImplementation deps.espresso.core 58 | implementation deps.support.recyclerview 59 | 60 | implementation 'com.github.xuexiangjys.XUtil:xutil-core:1.1.5' 61 | implementation 'com.github.xuexiangjys.XUtil:xutil-sub:1.1.5' 62 | //XPage 63 | implementation 'com.github.xuexiangjys.XPage:xpage-lib:2.2.2' 64 | annotationProcessor 'com.github.xuexiangjys.XPage:xpage-compiler:2.2.2' 65 | //butterKnife的sdk 66 | implementation 'com.jakewharton:butterknife:8.8.1' 67 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 68 | //如果开启了内存泄漏监测leak,就需要加上这个依赖 69 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' 70 | releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' 71 | testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' 72 | 73 | implementation project(':xfloatview-lib') 74 | 75 | // implementation 'com.github.xuexiangjys:XFloatView:1.0.0' 76 | } 77 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #=========================================基础不变的混淆配置=========================================## 2 | #指定代码的压缩级别 3 | -optimizationpasses 5 4 | #包名不混合大小写 5 | -dontusemixedcaseclassnames 6 | #不去忽略非公共的库类 7 | -dontskipnonpubliclibraryclasses 8 | # 指定不去忽略非公共的库的类的成员 9 | -dontskipnonpubliclibraryclassmembers 10 | #优化 不优化输入的类文件 11 | -dontoptimize 12 | #预校验 13 | -dontpreverify 14 | #混淆时是否记录日志 15 | -verbose 16 | # 混淆时所采用的算法 17 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 18 | #保护注解 19 | -keepattributes *Annotation* 20 | #忽略警告 21 | -ignorewarning 22 | 23 | ##记录生成的日志数据,gradle build时在本项目根目录输出## 24 | #apk 包内所有 class 的内部结构 25 | -dump class_files.txt 26 | #未混淆的类和成员 27 | -printseeds seeds.txt 28 | #列出从 apk 中删除的代码 29 | -printusage unused.txt 30 | #混淆前后的映射 31 | -printmapping mapping.txt 32 | # 并保留源文件名为"Proguard"字符串,而非原始的类名 并保留行号 33 | -keepattributes SourceFile,LineNumberTable 34 | ########记录生成的日志数据,gradle build时 在本项目根目录输出-end##### 35 | 36 | #需要保留的东西 37 | # 保持哪些类不被混淆 38 | -keep public class * extends android.app.Fragment 39 | -keep public class * extends android.app.Activity 40 | -keep public class * extends android.app.Application 41 | -keep public class * extends android.app.Service 42 | -keep public class * extends android.content.BroadcastReceiver 43 | -keep public class * extends android.content.ContentProvider 44 | -keep public class * extends android.app.backup.BackupAgentHelper 45 | -keep public class * extends android.preference.Preference 46 | -keep public class * extends android.support.v4.** 47 | -keep public class com.android.vending.licensing.ILicensingService 48 | 49 | #如果有引用v4包可以添加下面这行 50 | -keep public class * extends android.support.v4.app.Fragment 51 | 52 | ##########JS接口类不混淆,否则执行不了 53 | -dontwarn com.android.JsInterface.** 54 | -keep class com.android.JsInterface.** {*; } 55 | 56 | #极光推送和百度lbs android sdk一起使用proguard 混淆的问题#http的类被混淆后,导致apk定位失败,保持apache 的http类不被混淆就好了 57 | -dontwarn org.apache.** 58 | -keep class org.apache.**{ *; } 59 | 60 | -keep public class * extends android.view.View { 61 | public (android.content.Context); 62 | public (android.content.Context, android.util.AttributeSet); 63 | public (android.content.Context, android.util.AttributeSet, int); 64 | public void set*(...); 65 | } 66 | 67 | #保持 native 方法不被混淆 68 | -keepclasseswithmembernames class * { 69 | native ; 70 | } 71 | 72 | #保持自定义控件类不被混淆 73 | -keepclasseswithmembers class * { 74 | public (android.content.Context, android.util.AttributeSet); 75 | } 76 | 77 | #保持自定义控件类不被混淆 78 | -keepclassmembers class * extends android.app.Activity { 79 | public void *(android.view.View); 80 | } 81 | 82 | #保持 Parcelable 不被混淆 83 | -keep class * implements android.os.Parcelable { 84 | public static final android.os.Parcelable$Creator *; 85 | } 86 | 87 | #保持 Serializable 不被混淆 88 | -keepnames class * implements java.io.Serializable 89 | 90 | #保持 Serializable 不被混淆并且enum 类也不被混淆 91 | -keepclassmembers class * implements java.io.Serializable { 92 | static final long serialVersionUID; 93 | private static final java.io.ObjectStreamField[] serialPersistentFields; 94 | !static !transient ; 95 | !private ; 96 | !private ; 97 | private void writeObject(java.io.ObjectOutputStream); 98 | private void readObject(java.io.ObjectInputStream); 99 | java.lang.Object writeReplace(); 100 | java.lang.Object readResolve(); 101 | } 102 | 103 | #保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可 104 | -keepclassmembers enum * { 105 | public static **[] values(); 106 | public static ** valueOf(java.lang.String); 107 | } 108 | 109 | -keepclassmembers class * { 110 | public void *ButtonClicked(android.view.View); 111 | } 112 | 113 | #不混淆资源类 114 | -keep class **.R$* {*;} 115 | 116 | #===================================混淆保护自己项目的部分代码以及引用的第三方jar包library=============================####### 117 | #如果引用了v4或者v7包 118 | -dontwarn android.support.** 119 | 120 | # zxing 121 | -dontwarn com.google.zxing.** 122 | -keep class com.google.zxing.**{*;} 123 | 124 | #SignalR推送 125 | -keep class microsoft.aspnet.signalr.** { *; } 126 | 127 | # 极光推送混淆 128 | -dontoptimize 129 | -dontpreverify 130 | -dontwarn cn.jpush.** 131 | -keep class cn.jpush.** { *; } 132 | -dontwarn cn.jiguang.** 133 | -keep class cn.jiguang.** { *; } 134 | 135 | # 数据库框架OrmLite 136 | -keepattributes *DatabaseField* 137 | -keepattributes *DatabaseTable* 138 | -keepattributes *SerializedName* 139 | -keep class com.j256.** 140 | -keepclassmembers class com.j256.** { *; } 141 | -keep enum com.j256.** 142 | -keepclassmembers enum com.j256.** { *; } 143 | -keep interface com.j256.** 144 | -keepclassmembers interface com.j256.** { *; } 145 | 146 | #XHttp2 147 | -keep class com.xuexiang.xhttp2.model.** { *; } 148 | -keep class com.xuexiang.xhttp2.cache.model.** { *; } 149 | -keep class com.xuexiang.xhttp2.cache.stategy.**{*;} 150 | -keep class com.xuexiang.xhttp2.annotation.** { *; } 151 | 152 | #okhttp 153 | -dontwarn com.squareup.okhttp3.** 154 | -keep class com.squareup.okhttp3.** { *;} 155 | -dontwarn okio.** 156 | -dontwarn javax.annotation.Nullable 157 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 158 | -dontwarn javax.annotation.** 159 | 160 | #如果用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错 161 | -keepattributes Signature 162 | -keep class com.google.gson.stream.** { *; } 163 | -keepattributes EnclosingMethod 164 | -keep class org.xz_sale.entity.**{*;} 165 | -keep class com.google.gson.** {*;} 166 | -keep class com.google.**{*;} 167 | -keep class sun.misc.Unsafe { *; } 168 | -keep class com.google.gson.stream.** { *; } 169 | -keep class com.google.gson.examples.android.model.** { *; } 170 | 171 | # Retrofit 172 | -dontwarn retrofit2.** 173 | -keep class retrofit2.** { *; } 174 | -keepattributes Exceptions#XHt 175 | 176 | # RxJava RxAndroid 177 | -dontwarn sun.misc.** 178 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 179 | long producerIndex; 180 | long consumerIndex; 181 | } 182 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 183 | rx.internal.util.atomic.LinkedQueueNode producerNode; 184 | } 185 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 186 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 187 | } 188 | 189 | -dontwarn okio.** 190 | -dontwarn javax.annotation.Nullable 191 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 192 | -dontwarn javax.annotation.** 193 | 194 | # fastjson 195 | -dontwarn com.alibaba.fastjson.** 196 | -keep class com.alibaba.fastjson.** { *; } 197 | -keepattributes Signature 198 | 199 | # xpage 200 | -keep class com.xuexiang.xpage.annotation.** { *; } 201 | 202 | # xaop 203 | -keep @com.xuexiang.xaop.annotation.* class * {*;} 204 | -keep class * { 205 | @com.xuexiang.xaop.annotation.* ; 206 | } 207 | -keepclassmembers class * { 208 | @com.xuexiang.xaop.annotation.* ; 209 | } 210 | 211 | # xrouter 212 | -keep public class com.xuexiang.xrouter.routes.**{*;} 213 | -keep class * implements com.xuexiang.xrouter.facade.template.ISyringe{*;} 214 | # 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口 215 | -keep interface * implements com.xuexiang.xrouter.facade.template.IProvider 216 | # 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现 217 | -keep class * implements com.xuexiang.xrouter.facade.template.IProvider 218 | 219 | # xupdate 220 | -keep class com.xuexiang.xupdate.entity.** { *; } 221 | 222 | # xvideo 223 | -keep class com.xuexiang.xvideo.jniinterface.** { *; } 224 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xuexiang/xfloatview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.xfloatview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xuexiang.xfloatview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/App.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo; 18 | 19 | import android.app.Application; 20 | import android.content.Context; 21 | 22 | import com.xuexiang.xpage.AppPageConfig; 23 | import com.xuexiang.xpage.PageConfig; 24 | import com.xuexiang.xpage.PageConfiguration; 25 | import com.xuexiang.xpage.model.PageInfo; 26 | import com.xuexiang.xutil.XUtil; 27 | 28 | import java.util.List; 29 | 30 | /** 31 | * @author xuexiang 32 | * @since 2018/9/13 上午2:29 33 | */ 34 | public class App extends Application { 35 | 36 | @Override 37 | public void onCreate() { 38 | super.onCreate(); 39 | 40 | XUtil.init(this); 41 | XUtil.debug(true); 42 | 43 | PageConfig.getInstance().setPageConfiguration(new PageConfiguration() { //页面注册 44 | @Override 45 | public List registerPages(Context context) { 46 | return AppPageConfig.getInstance().getPages(); //自动注册页面 47 | } 48 | }).debug("PageLog").enableWatcher(true).init(this); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.activity; 18 | 19 | import android.os.Bundle; 20 | 21 | import com.xuexiang.xfloatviewdemo.fragment.MainFragment; 22 | import com.xuexiang.xpage.base.XPageActivity; 23 | 24 | public class MainActivity extends XPageActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | 30 | openPage(MainFragment.class); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/adapter/AppTrafficAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.adapter; 18 | 19 | import android.content.Context; 20 | import android.content.pm.PackageManager; 21 | import android.text.format.Formatter; 22 | 23 | import com.xuexiang.xfloatviewdemo.R; 24 | import com.xuexiang.xfloatviewdemo.entity.AppTrafficInfo; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * 流量信息适配器 30 | * 31 | * @author xuexiang 32 | * @since 2018/9/13 下午3:12 33 | */ 34 | public class AppTrafficAdapter extends BaseRecyclerAdapter { 35 | private PackageManager mPackageManager; 36 | 37 | public AppTrafficAdapter(Context context, List list) { 38 | super(context, list); 39 | mPackageManager = context.getPackageManager(); 40 | } 41 | 42 | @Override 43 | public int getItemLayoutId(int viewType) { 44 | return R.layout.adapter_recycler_app_traffic_item; 45 | } 46 | 47 | @Override 48 | public void bindData(RecyclerViewHolder holder, int position, AppTrafficInfo item) { 49 | holder.getImageView(R.id.iv_app_launcher).setImageDrawable(item.getApplicationInfo().loadIcon(mPackageManager)); 50 | holder.getTextView(R.id.tv_app_name).setText(mPackageManager.getApplicationLabel(item.getApplicationInfo())); 51 | holder.getTextView(R.id.tv_app_download).setText(String.format("下载:%s", Formatter.formatFileSize(getContext(), item.getDownLoadBytes()))); 52 | holder.getTextView(R.id.tv_app_upload).setText(String.format("上传:%s", Formatter.formatFileSize(getContext(), item.getUpLoadBytes()))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/adapter/BaseRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.adapter; 18 | 19 | import android.content.Context; 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | /** 29 | * 基础适配器 30 | * 31 | * @author xuexiang 32 | * @since 2018/9/13 下午3:13 33 | */ 34 | public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter { 35 | protected final List mData; 36 | protected final Context mContext; 37 | protected LayoutInflater mInflater; 38 | private OnItemClickListener mClickListener; 39 | private OnItemLongClickListener mLongClickListener; 40 | 41 | public BaseRecyclerAdapter(Context context, List list) { 42 | mData = (list != null) ? list : new ArrayList(); 43 | mContext = context; 44 | mInflater = LayoutInflater.from(context); 45 | } 46 | 47 | @Override 48 | public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 49 | final RecyclerViewHolder holder = new RecyclerViewHolder(mContext, 50 | mInflater.inflate(getItemLayoutId(viewType), parent, false)); 51 | if (mClickListener != null) { 52 | holder.itemView.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | mClickListener.onItemClick(holder.itemView, holder.getLayoutPosition()); 56 | } 57 | }); 58 | } 59 | if (mLongClickListener != null) { 60 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { 61 | @Override 62 | public boolean onLongClick(View v) { 63 | mLongClickListener.onItemLongClick(holder.itemView, holder.getLayoutPosition()); 64 | return true; 65 | } 66 | }); 67 | } 68 | return holder; 69 | } 70 | 71 | @Override 72 | public void onBindViewHolder(RecyclerViewHolder holder, int position) { 73 | bindData(holder, position, mData.get(position)); 74 | } 75 | 76 | public T getItem(int pos) { 77 | return mData.get(pos); 78 | } 79 | 80 | @Override 81 | public int getItemCount() { 82 | return mData.size(); 83 | } 84 | 85 | public void add(int pos, T item) { 86 | mData.add(pos, item); 87 | notifyItemInserted(pos); 88 | } 89 | 90 | public void delete(int pos) { 91 | mData.remove(pos); 92 | notifyItemRemoved(pos); 93 | } 94 | 95 | public Context getContext() { 96 | return mContext; 97 | } 98 | 99 | public void setOnItemClickListener(OnItemClickListener listener) { 100 | mClickListener = listener; 101 | } 102 | 103 | public void setOnItemLongClickListener(OnItemLongClickListener listener) { 104 | mLongClickListener = listener; 105 | } 106 | 107 | abstract public int getItemLayoutId(int viewType); 108 | 109 | abstract public void bindData(RecyclerViewHolder holder, int position, T item); 110 | 111 | public interface OnItemClickListener { 112 | void onItemClick(View itemView, int pos); 113 | } 114 | 115 | public interface OnItemLongClickListener { 116 | void onItemLongClick(View itemView, int pos); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/adapter/RecyclerViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.adapter; 18 | 19 | import android.content.Context; 20 | import android.support.v7.widget.RecyclerView; 21 | import android.util.SparseArray; 22 | import android.view.View; 23 | import android.widget.Button; 24 | import android.widget.EditText; 25 | import android.widget.ImageButton; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | 29 | /** 30 | * 31 | * @author xuexiang 32 | * @date 2018/3/21 下午11:18 33 | */ 34 | public class RecyclerViewHolder extends RecyclerView.ViewHolder { 35 | private SparseArray mViews; 36 | private Context mContext; 37 | 38 | public RecyclerViewHolder(Context context, View itemView) { 39 | super(itemView); 40 | mContext = context; 41 | mViews = new SparseArray<>(); 42 | } 43 | 44 | private T findViewById(int viewId) { 45 | View view = mViews.get(viewId); 46 | if (view == null) { 47 | view = itemView.findViewById(viewId); 48 | mViews.put(viewId, view); 49 | } 50 | return (T) view; 51 | } 52 | 53 | public View getView(int viewId) { 54 | return findViewById(viewId); 55 | } 56 | 57 | public TextView getTextView(int viewId) { 58 | return (TextView) getView(viewId); 59 | } 60 | 61 | public Button getButton(int viewId) { 62 | return (Button) getView(viewId); 63 | } 64 | 65 | public ImageView getImageView(int viewId) { 66 | return (ImageView) getView(viewId); 67 | } 68 | 69 | public ImageButton getImageButton(int viewId) { 70 | return (ImageButton) getView(viewId); 71 | } 72 | 73 | public EditText getEditText(int viewId) { 74 | return (EditText) getView(viewId); 75 | } 76 | 77 | public RecyclerViewHolder setText(int viewId, String value) { 78 | TextView view = findViewById(viewId); 79 | view.setText(value); 80 | return this; 81 | } 82 | 83 | public RecyclerViewHolder setBackground(int viewId, int resId) { 84 | View view = findViewById(viewId); 85 | view.setBackgroundResource(resId); 86 | return this; 87 | } 88 | 89 | public RecyclerViewHolder setClickListener(int viewId, View.OnClickListener listener) { 90 | View view = findViewById(viewId); 91 | view.setOnClickListener(listener); 92 | return this; 93 | } 94 | 95 | public Context getContext() { 96 | return mContext; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/entity/AppTrafficInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.entity; 18 | 19 | import android.content.pm.ApplicationInfo; 20 | 21 | /** 22 | * 应用流量信息 23 | * 24 | * @author xuexiang 25 | * @since 2018/9/13 下午3:12 26 | */ 27 | public class AppTrafficInfo { 28 | 29 | /** 30 | * 应用信息 31 | */ 32 | private ApplicationInfo mApplicationInfo; 33 | 34 | /** 35 | * 上传流量 36 | */ 37 | private long mUpLoadBytes; 38 | /** 39 | * 下载流量 40 | */ 41 | private long mDownLoadBytes; 42 | 43 | public AppTrafficInfo(ApplicationInfo applicationInfo, long upLoadBytes, long downLoadBytes) { 44 | mApplicationInfo = applicationInfo; 45 | mUpLoadBytes = upLoadBytes; 46 | mDownLoadBytes = downLoadBytes; 47 | } 48 | 49 | public ApplicationInfo getApplicationInfo() { 50 | return mApplicationInfo; 51 | } 52 | 53 | public AppTrafficInfo setApplicationInfo(ApplicationInfo applicationInfo) { 54 | mApplicationInfo = applicationInfo; 55 | return this; 56 | } 57 | 58 | 59 | public long getUpLoadBytes() { 60 | return mUpLoadBytes; 61 | } 62 | 63 | public AppTrafficInfo setUpLoadBytes(long upLoadBytes) { 64 | mUpLoadBytes = upLoadBytes; 65 | return this; 66 | } 67 | 68 | public long getDownLoadBytes() { 69 | return mDownLoadBytes; 70 | } 71 | 72 | public AppTrafficInfo setDownLoadBytes(long downLoadBytes) { 73 | mDownLoadBytes = downLoadBytes; 74 | return this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/fragment/AppTrafficFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.fragment; 18 | 19 | import android.Manifest; 20 | import android.content.pm.ApplicationInfo; 21 | import android.content.pm.PackageInfo; 22 | import android.content.pm.PackageManager; 23 | import android.net.TrafficStats; 24 | import android.support.v7.widget.DefaultItemAnimator; 25 | import android.support.v7.widget.DividerItemDecoration; 26 | import android.support.v7.widget.LinearLayoutManager; 27 | import android.support.v7.widget.RecyclerView; 28 | import android.view.View; 29 | 30 | import com.xuexiang.xfloatviewdemo.R; 31 | import com.xuexiang.xfloatviewdemo.adapter.AppTrafficAdapter; 32 | import com.xuexiang.xfloatviewdemo.adapter.BaseRecyclerAdapter; 33 | import com.xuexiang.xfloatviewdemo.entity.AppTrafficInfo; 34 | import com.xuexiang.xfloatviewdemo.service.NetWorkMonitorService; 35 | import com.xuexiang.xpage.annotation.Page; 36 | import com.xuexiang.xpage.base.XPageFragment; 37 | import com.xuexiang.xutil.app.AppUtils; 38 | import com.xuexiang.xutil.app.PackageUtils; 39 | 40 | import java.util.ArrayList; 41 | import java.util.List; 42 | 43 | import butterknife.BindView; 44 | 45 | /** 46 | * @author xuexiang 47 | * @since 2018/9/13 下午3:14 48 | */ 49 | @Page(name = "应用流量详情列表") 50 | public class AppTrafficFragment extends XPageFragment { 51 | 52 | @BindView(R.id.recycler_view) 53 | RecyclerView mRecyclerView; 54 | 55 | private AppTrafficAdapter mAppTrafficAdapter; 56 | 57 | private List mAppInfos = new ArrayList<>(); 58 | 59 | @Override 60 | protected int getLayoutId() { 61 | return R.layout.activity_app_traffic; 62 | } 63 | 64 | @Override 65 | protected void initViews() { 66 | mRecyclerView.setItemAnimator(new DefaultItemAnimator()); 67 | mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); 68 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 69 | 70 | mAppTrafficAdapter = new AppTrafficAdapter(getContext(), mAppInfos); 71 | mRecyclerView.setAdapter(mAppTrafficAdapter); 72 | 73 | updateAppTrafficInfo(); 74 | } 75 | 76 | @Override 77 | protected void initListeners() { 78 | mAppTrafficAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { 79 | @Override 80 | public void onItemClick(View itemView, int pos) { 81 | ApplicationInfo applicationInfo = mAppTrafficAdapter.getItem(pos).getApplicationInfo(); 82 | NetWorkMonitorService.start(getContext(), applicationInfo); 83 | PackageUtils.switchApp(getContext(), applicationInfo.packageName); 84 | } 85 | }); 86 | } 87 | 88 | private void updateAppTrafficInfo() { 89 | PackageManager pm = AppUtils.getPackageManager(); 90 | List packageInfos = pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_PERMISSIONS); 91 | for (PackageInfo info : packageInfos) { 92 | String[] permissions = info.requestedPermissions; 93 | if (permissions != null && permissions.length > 0) { 94 | for (String permission : permissions) { 95 | if (Manifest.permission.INTERNET.equals(permission)) { 96 | int uid = info.applicationInfo.uid; 97 | long upLoadBytes = TrafficStats.getUidTxBytes(uid); 98 | long downLoadBytes = TrafficStats.getUidRxBytes(uid); 99 | 100 | mAppInfos.add(new AppTrafficInfo(info.applicationInfo, upLoadBytes, downLoadBytes)); 101 | 102 | // /** 获取手机通过 2G/3G 接收的字节流量总数 */ 103 | // TrafficStats.getMobileRxBytes(); 104 | // /** 获取手机通过 2G/3G 接收的数据包总数 */ 105 | // TrafficStats.getMobileRxPackets(); 106 | // /** 获取手机通过 2G/3G 发出的字节流量总数 */ 107 | // TrafficStats.getMobileTxBytes(); 108 | // /** 获取手机通过 2G/3G 发出的数据包总数 */ 109 | // TrafficStats.getMobileTxPackets(); 110 | // /** 获取手机通过所有网络方式接收的字节流量总数(包括 wifi) */ 111 | // TrafficStats.getTotalRxBytes(); 112 | // /** 获取手机通过所有网络方式接收的数据包总数(包括 wifi) */ 113 | // TrafficStats.getTotalRxPackets(); 114 | // /** 获取手机通过所有网络方式发送的字节流量总数(包括 wifi) */ 115 | // TrafficStats.getTotalTxBytes(); 116 | // /** 获取手机通过所有网络方式发送的数据包总数(包括 wifi) */ 117 | // TrafficStats.getTotalTxPackets(); 118 | // /** 获取手机指定 UID 对应的应程序用通过所有网络方式接收的字节流量总数(包括 wifi) */ 119 | // TrafficStats.getUidRxBytes(uid); 120 | // /** 获取手机指定 UID 对应的应用程序通过所有网络方式发送的字节流量总数(包括 wifi) */ 121 | // TrafficStats.getUidTxBytes(uid); 122 | } 123 | } 124 | } 125 | } 126 | mAppTrafficAdapter.notifyDataSetChanged(); 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/fragment/MainFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.fragment; 18 | 19 | import android.view.KeyEvent; 20 | import android.view.View; 21 | 22 | import com.xuexiang.xpage.annotation.Page; 23 | import com.xuexiang.xpage.base.XPageContainerListFragment; 24 | import com.xuexiang.xpage.utils.TitleBar; 25 | import com.xuexiang.xutil.common.ClickUtils; 26 | 27 | /** 28 | * @author xuexiang 29 | * @since 2018/9/13 上午2:30 30 | */ 31 | @Page(name = "XFloatView 悬浮窗") 32 | public class MainFragment extends XPageContainerListFragment { 33 | 34 | @Override 35 | protected Class[] getPagesClasses() { 36 | return new Class[] { 37 | NetWorkMonitorFragment.class, 38 | SmartViewFragment.class 39 | }; 40 | } 41 | 42 | @Override 43 | protected TitleBar initTitleBar() { 44 | return super.initTitleBar().setLeftClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View view) { 47 | ClickUtils.exitBy2Click(); 48 | } 49 | }); 50 | } 51 | 52 | 53 | /** 54 | * 菜单、返回键响应 55 | */ 56 | @Override 57 | public boolean onKeyDown(int keyCode, KeyEvent event) { 58 | if (keyCode == KeyEvent.KEYCODE_BACK) { 59 | ClickUtils.exitBy2Click(); 60 | } 61 | return true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/fragment/NetWorkMonitorFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.fragment; 18 | 19 | import com.xuexiang.xfloatview.permission.FloatWindowPermission; 20 | import com.xuexiang.xfloatviewdemo.service.NetWorkMonitorService; 21 | import com.xuexiang.xpage.annotation.Page; 22 | import com.xuexiang.xpage.base.XPageSimpleListFragment; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * @author xuexiang 28 | * @since 2018/9/13 上午11:28 29 | */ 30 | @Page(name = "网速监测服务") 31 | public class NetWorkMonitorFragment extends XPageSimpleListFragment { 32 | 33 | @Override 34 | protected void initArgs() { 35 | super.initArgs(); 36 | // PermissionUtils.requestSystemAlertWindow(getActivity()); 37 | FloatWindowPermission.getInstance().applyFloatWindowPermission(getContext()); 38 | } 39 | 40 | @Override 41 | protected List initSimpleData(List lists) { 42 | lists.add("启动"); 43 | lists.add("关闭"); 44 | lists.add("选择应用监测"); 45 | return lists; 46 | } 47 | 48 | @Override 49 | protected void onItemClick(int position) { 50 | switch(position) { 51 | case 0: 52 | NetWorkMonitorService.start(getContext(), null); 53 | break; 54 | case 1: 55 | NetWorkMonitorService.stop(getContext()); 56 | break; 57 | case 2: 58 | openPage(AppTrafficFragment.class); 59 | default: 60 | break; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/fragment/SmartViewFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.fragment; 18 | 19 | import com.xuexiang.xfloatview.permission.FloatWindowPermission; 20 | import com.xuexiang.xfloatviewdemo.service.SmartViewService; 21 | import com.xuexiang.xpage.annotation.Page; 22 | import com.xuexiang.xpage.base.XPageSimpleListFragment; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * @author xuexiang 28 | * @since 2018/9/13 下午2:34 29 | */ 30 | @Page(name = "智能控制盘") 31 | public class SmartViewFragment extends XPageSimpleListFragment { 32 | 33 | @Override 34 | protected void initArgs() { 35 | super.initArgs(); 36 | // PermissionUtils.requestSystemAlertWindow(getActivity()); 37 | FloatWindowPermission.getInstance().applyFloatWindowPermission(getContext()); 38 | } 39 | 40 | @Override 41 | protected List initSimpleData(List lists) { 42 | lists.add("启动"); 43 | lists.add("关闭"); 44 | return lists; 45 | } 46 | 47 | @Override 48 | protected void onItemClick(int position) { 49 | switch(position) { 50 | case 0: 51 | SmartViewService.start(getContext()); 52 | break; 53 | case 1: 54 | SmartViewService.stop(getContext()); 55 | break; 56 | default: 57 | break; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/service/NetWorkMonitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.service; 18 | 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.net.TrafficStats; 22 | 23 | import static com.xuexiang.xfloatviewdemo.service.NetWorkMonitorService.ACTION_NETWORK_SPEED_INFO; 24 | import static com.xuexiang.xfloatviewdemo.service.NetWorkMonitorService.KEY_DOWNLOAD_SPEED; 25 | import static com.xuexiang.xfloatviewdemo.service.NetWorkMonitorService.KEY_UPLOAD_SPEED; 26 | 27 | /** 28 | * 网络监听线程 29 | * 30 | * @author xuexiang 31 | * @since 2018/9/13 上午11:41 32 | */ 33 | public class NetWorkMonitor extends Thread { 34 | 35 | private Context mContext; 36 | /** 37 | * 是否在运行 38 | */ 39 | private boolean mIsRunning; 40 | 41 | /** 42 | * 检测间隔(单位:秒) 43 | */ 44 | private int mKeepAliveInterval = 1; 45 | 46 | private OnNetWorkListener mOnNetWorkListener; 47 | 48 | /** 49 | * 监听类型 50 | */ 51 | private int mMonitorType; 52 | 53 | /** 54 | * 应用id 55 | */ 56 | private int mUid; 57 | 58 | /** 59 | * 总的发送字节数(上行) 60 | */ 61 | private long mTotalUpLoadBytes = 0; 62 | /** 63 | * 总的接收字节数(下行) 64 | */ 65 | private long mTotalDownLoadBytes = 0; 66 | /** 67 | * 上行速度 68 | */ 69 | private double mUpLoadSpeed = 0; 70 | /** 71 | * 下行速度 72 | */ 73 | private double mDownLoadSpeed = 0; 74 | /** 75 | * 构造器 76 | */ 77 | public NetWorkMonitor(Context context, int keepAliveInterval, OnNetWorkListener listener) { 78 | mIsRunning = true; 79 | mContext = context.getApplicationContext(); 80 | mKeepAliveInterval = keepAliveInterval; 81 | mOnNetWorkListener = listener; 82 | } 83 | 84 | public NetWorkMonitor updateMonitorType(int monitorType, int uid) { 85 | mMonitorType = monitorType; 86 | mUid = uid; 87 | if (mMonitorType == NetWorkMonitorService.MONITOR_TYPE_ALL_APP) { 88 | mTotalUpLoadBytes = TrafficStats.getTotalTxBytes(); 89 | mTotalDownLoadBytes = TrafficStats.getTotalRxBytes(); 90 | } else { 91 | mTotalUpLoadBytes = TrafficStats.getUidTxBytes(mUid); 92 | mTotalDownLoadBytes = TrafficStats.getUidRxBytes(mUid); 93 | } 94 | return this; 95 | } 96 | 97 | public void run() { 98 | while (mIsRunning) { 99 | try { 100 | // 休息一段时间 101 | Thread.sleep(mKeepAliveInterval * 1000); 102 | doNetWorkCheck(); 103 | } catch (InterruptedException e) { 104 | e.printStackTrace(); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * 进行网络状态的检测 111 | */ 112 | private void doNetWorkCheck() { 113 | long tempTotalUpLoadBytes, tempTotalDownLoadBytes; 114 | 115 | if (mMonitorType == NetWorkMonitorService.MONITOR_TYPE_ALL_APP) { 116 | tempTotalUpLoadBytes = TrafficStats.getTotalTxBytes(); 117 | tempTotalDownLoadBytes = TrafficStats.getTotalRxBytes(); 118 | } else { 119 | tempTotalUpLoadBytes = TrafficStats.getUidTxBytes(mUid); 120 | tempTotalDownLoadBytes = TrafficStats.getUidRxBytes(mUid); 121 | } 122 | 123 | mUpLoadSpeed = (tempTotalUpLoadBytes - mTotalUpLoadBytes) / mKeepAliveInterval; 124 | mDownLoadSpeed = (tempTotalDownLoadBytes - mTotalDownLoadBytes) / mKeepAliveInterval; 125 | 126 | sendNetWorkInfo(mUpLoadSpeed, mDownLoadSpeed); 127 | 128 | mTotalUpLoadBytes = tempTotalUpLoadBytes; 129 | mTotalDownLoadBytes = tempTotalDownLoadBytes; 130 | } 131 | 132 | /** 133 | * 发送网络信息 134 | * @param upLoadSpeed 135 | * @param downLoadSpeed 136 | */ 137 | private void sendNetWorkInfo(double upLoadSpeed, double downLoadSpeed) { 138 | if (mOnNetWorkListener != null) { 139 | mOnNetWorkListener.onUpdateSpeed(upLoadSpeed, downLoadSpeed); 140 | } else { 141 | Intent intent = new Intent(ACTION_NETWORK_SPEED_INFO); 142 | intent.putExtra(KEY_UPLOAD_SPEED, upLoadSpeed); 143 | intent.putExtra(KEY_DOWNLOAD_SPEED, downLoadSpeed); 144 | mContext.sendBroadcast(intent); 145 | } 146 | } 147 | 148 | /** 149 | * 关闭线程 150 | */ 151 | public void closeThread() { 152 | mIsRunning = false; 153 | mOnNetWorkListener = null; 154 | interrupt(); 155 | } 156 | 157 | public NetWorkMonitor setOnNetWorkListener(OnNetWorkListener onNetWorkListener) { 158 | mOnNetWorkListener = onNetWorkListener; 159 | return this; 160 | } 161 | 162 | /** 163 | * 网络监听 164 | */ 165 | public interface OnNetWorkListener { 166 | /** 167 | * 更新速度 168 | * @param upLoadSpeed 上行速度 169 | * @param downLoadSpeed 下行速度 170 | */ 171 | void onUpdateSpeed(double upLoadSpeed, double downLoadSpeed); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/service/NetWorkMonitorService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.service; 18 | 19 | import android.app.Notification; 20 | import android.app.NotificationChannel; 21 | import android.app.NotificationManager; 22 | import android.app.Service; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.SharedPreferences; 26 | import android.content.pm.ApplicationInfo; 27 | import android.os.Build; 28 | import android.os.IBinder; 29 | import android.support.annotation.Nullable; 30 | import android.support.annotation.RequiresApi; 31 | 32 | import com.xuexiang.xfloatviewdemo.widget.MonitorView; 33 | import com.xuexiang.xutil.data.SPUtils; 34 | 35 | import java.util.Objects; 36 | 37 | /** 38 | * 网络状态监听服务 39 | * 40 | * @author xuexiang 41 | * @since 2018/9/13 上午11:39 42 | */ 43 | public class NetWorkMonitorService extends Service { 44 | 45 | public final static String ACTION_NETWORK_SPEED_INFO = "com.xuexiang.devicemonitor.ACTION_NETWORK_SPEED_INFO"; 46 | 47 | public final static String KEY_UPLOAD_SPEED = "com.xuexiang.devicemonitor.key_upload_speed"; 48 | 49 | public final static String KEY_DOWNLOAD_SPEED = "com.xuexiang.devicemonitor.key_download_speed"; 50 | 51 | public final static String KEY_MONITOR_TYPE = "com.xuexiang.devicemonitor.key_monitor_type"; 52 | public final static String KEY_APP_UID = "com.xuexiang.devicemonitor.key_app_uid"; 53 | public final static String KEY_APP_NAME = "com.xuexiang.devicemonitor.key_app_name"; 54 | 55 | public final static int MONITOR_TYPE_ALL_APP = 0; 56 | public final static int MONITOR_TYPE_SINGLE_APP = 1; 57 | 58 | private MonitorView mMonitorView; 59 | 60 | private NetWorkMonitor mNetWorkMonitor; 61 | 62 | public static final String CHANNEL_ID = "NetWorkMonitor"; 63 | 64 | @Nullable 65 | @Override 66 | public IBinder onBind(Intent intent) { 67 | return null; 68 | } 69 | 70 | @Override 71 | public int onStartCommand(Intent intent, int flags, int startId) { 72 | SharedPreferences sp = SPUtils.getDefaultSharedPreferences(); 73 | int monitorType, uid; 74 | String appName; 75 | if (intent != null) { 76 | monitorType = intent.getIntExtra(KEY_MONITOR_TYPE, MONITOR_TYPE_ALL_APP); 77 | uid = intent.getIntExtra(KEY_APP_UID, 0); 78 | appName = intent.getStringExtra(KEY_APP_NAME); 79 | 80 | SPUtils.putInt(sp, KEY_MONITOR_TYPE, monitorType); 81 | SPUtils.putInt(sp, KEY_APP_UID, uid); 82 | SPUtils.putString(sp, KEY_APP_NAME, appName); 83 | } else { 84 | monitorType = SPUtils.getInt(sp, KEY_MONITOR_TYPE, MONITOR_TYPE_ALL_APP); 85 | uid = SPUtils.getInt(sp, KEY_APP_UID, 0); 86 | appName = SPUtils.getString(sp, KEY_APP_NAME, "null"); 87 | } 88 | init(monitorType, uid, appName); 89 | return super.onStartCommand(intent, flags, startId); 90 | } 91 | 92 | /** 93 | * 开启监测 94 | * @param info 95 | */ 96 | public static void start(Context context, ApplicationInfo info) { 97 | Intent intent = new Intent(context, NetWorkMonitorService.class); 98 | if (info == null) { 99 | intent.putExtra(KEY_MONITOR_TYPE, MONITOR_TYPE_ALL_APP); 100 | } else { 101 | intent.putExtra(KEY_MONITOR_TYPE, MONITOR_TYPE_SINGLE_APP); 102 | intent.putExtra(KEY_APP_UID, info.uid); 103 | intent.putExtra(KEY_APP_NAME, context.getPackageManager().getApplicationLabel(info)); 104 | } 105 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 106 | context.startForegroundService(intent); 107 | } else { 108 | context.startService(intent); 109 | } 110 | } 111 | 112 | /** 113 | * 停止监测 114 | * @param context 115 | */ 116 | public static void stop(Context context) { 117 | SharedPreferences sp = SPUtils.getDefaultSharedPreferences(); 118 | SPUtils.clear(sp); 119 | context.stopService(new Intent(context, NetWorkMonitorService.class)); 120 | } 121 | 122 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 123 | @Override 124 | public void onCreate() { 125 | super.onCreate(); 126 | //此处为创建前台服务,但是通知栏消息为空,这样我们就可 以在不通知用户的情况下启动前台服务了 127 | Notification.Builder builder; 128 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 129 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 130 | NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "流量监测", NotificationManager.IMPORTANCE_HIGH); 131 | Objects.requireNonNull(manager).createNotificationChannel(channel); 132 | builder = new Notification.Builder(this, CHANNEL_ID); 133 | } else { 134 | builder = new Notification.Builder(this); 135 | } 136 | startForeground(100, builder.setContentTitle("流量监测") 137 | .setContentText("应用正在监测手机的流量") 138 | .setAutoCancel(false) 139 | .setOngoing(true) 140 | .build()); 141 | } 142 | 143 | private void init(int monitorType, int uid, String appName) { 144 | if (mMonitorView == null) { 145 | mMonitorView = new MonitorView(this); 146 | mMonitorView.show(); 147 | } 148 | mMonitorView.updateMonitorAppName(appName); 149 | 150 | if (mNetWorkMonitor == null) { 151 | mNetWorkMonitor = new NetWorkMonitor(this, 1, new NetWorkMonitor.OnNetWorkListener() { 152 | /** 153 | * 更新速度 154 | * 155 | * @param upLoadSpeed 上行速度 156 | * @param downLoadSpeed 下行速度 157 | */ 158 | @Override 159 | public void onUpdateSpeed(double upLoadSpeed, double downLoadSpeed) { 160 | if (mMonitorView != null) { 161 | mMonitorView.updateNetWorkInfo(upLoadSpeed, downLoadSpeed); 162 | } 163 | } 164 | }); 165 | mNetWorkMonitor.updateMonitorType(monitorType, uid).start(); 166 | } else { 167 | mNetWorkMonitor.updateMonitorType(monitorType, uid); 168 | } 169 | 170 | } 171 | 172 | @Override 173 | public void onDestroy() { 174 | if (mNetWorkMonitor != null) { 175 | mNetWorkMonitor.closeThread(); 176 | mNetWorkMonitor = null; 177 | } 178 | if (mMonitorView != null) { 179 | mMonitorView.clear(); 180 | mMonitorView = null; 181 | } 182 | super.onDestroy(); 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/service/SmartViewService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.service; 18 | 19 | import android.app.Notification; 20 | import android.app.NotificationChannel; 21 | import android.app.NotificationManager; 22 | import android.app.Service; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.res.Configuration; 26 | import android.os.Build; 27 | import android.os.IBinder; 28 | import android.support.annotation.RequiresApi; 29 | 30 | import com.xuexiang.xfloatviewdemo.widget.smart.SmartView; 31 | 32 | import java.util.Objects; 33 | 34 | /** 35 | * 按钮控制盘的监听服务 36 | * 37 | * @author xuexiang 38 | * @since 2018/9/13 下午2:31 39 | */ 40 | public class SmartViewService extends Service { 41 | 42 | private SmartView mSmartView; 43 | 44 | private static boolean isRunning = false; 45 | 46 | public static final String CHANNEL_ID = "SmartView"; 47 | 48 | /** 49 | * 开启 50 | * 51 | * @param context 52 | */ 53 | public static void start(Context context) { 54 | if (!SmartViewService.isRunning()) { 55 | Intent intent = new Intent(context, SmartViewService.class); 56 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 57 | context.startForegroundService(intent); 58 | } else { 59 | context.startService(intent); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * 关闭 66 | * 67 | * @param context 68 | */ 69 | public static void stop(Context context) { 70 | if (SmartViewService.isRunning()) { 71 | context.stopService(new Intent(context, SmartViewService.class)); 72 | } 73 | } 74 | 75 | @Override 76 | public IBinder onBind(Intent intent) { 77 | return null; 78 | } 79 | 80 | @Override 81 | public boolean onUnbind(Intent intent) { 82 | return super.onUnbind(intent); 83 | } 84 | 85 | /** 86 | * 判断服务是否运行 87 | */ 88 | public static boolean isRunning() { 89 | return isRunning; 90 | } 91 | 92 | 93 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 94 | public void onCreate() { 95 | super.onCreate(); 96 | init(); 97 | } 98 | 99 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 100 | private void init() { 101 | if (mSmartView == null) { 102 | mSmartView = new SmartView(this); 103 | } 104 | isRunning = true; 105 | //此处为创建前台服务,但是通知栏消息为空,这样我们就可 以在不通知用户的情况下启动前台服务了 106 | Notification.Builder builder; 107 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 108 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 109 | NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "按钮救星", NotificationManager.IMPORTANCE_HIGH); 110 | Objects.requireNonNull(manager).createNotificationChannel(channel); 111 | builder = new Notification.Builder(this, CHANNEL_ID); 112 | } else { 113 | builder = new Notification.Builder(this); 114 | } 115 | startForeground(101, builder.setContentTitle("按钮救星") 116 | .setContentText("按钮救星正在运行...") 117 | .setAutoCancel(false) 118 | .setOngoing(true) 119 | .build()); 120 | } 121 | 122 | public void onDestroy() { 123 | if (mSmartView != null) { 124 | mSmartView.destroy(); 125 | mSmartView = null; 126 | } 127 | isRunning = false; 128 | super.onDestroy(); 129 | } 130 | 131 | /** 132 | * 屏幕旋转时调用此方法 133 | */ 134 | @Override 135 | public void onConfigurationChanged(Configuration newConfig) { 136 | super.onConfigurationChanged(newConfig); 137 | if (mSmartView != null) { 138 | mSmartView.refreshSmartView(); 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/util/SystemKeyboard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.util; 18 | 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.media.AudioManager; 22 | import android.view.KeyEvent; 23 | 24 | import com.xuexiang.xutil.common.ShellUtils; 25 | import com.xuexiang.xutil.system.AppExecutors; 26 | 27 | /** 28 | * 系统快捷键 29 | * 30 | * @author xuexiang 31 | * @since 2018/9/13 下午2:21 32 | */ 33 | public class SystemKeyboard { 34 | 35 | /** 36 | * 回到主界面 37 | * 38 | * @param context 39 | */ 40 | public static void toHome(Context context) { 41 | Intent i = new Intent(); 42 | i.setAction(Intent.ACTION_MAIN); 43 | i.addCategory(Intent.CATEGORY_HOME); 44 | i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 45 | context.startActivity(i); 46 | 47 | } 48 | 49 | /** 50 | * 返回按钮 51 | */ 52 | public static void toBack() { 53 | AppExecutors.get().poolIO().execute(new Runnable() { 54 | @Override 55 | public void run() { 56 | sendKeyevent(KeyEvent.KEYCODE_BACK); 57 | } 58 | }); 59 | } 60 | 61 | /** 62 | * 发送按钮事件 63 | * 64 | * @param eventCode 65 | */ 66 | public static void sendKeyevent(int eventCode) { 67 | ShellUtils.execCommand("input keyevent " + eventCode, true, false); 68 | } 69 | 70 | /** 71 | * 菜单按钮 72 | */ 73 | public static void toMenu() { 74 | AppExecutors.get().poolIO().execute(new Runnable() { 75 | @Override 76 | public void run() { 77 | sendKeyevent(KeyEvent.KEYCODE_MENU); 78 | } 79 | }); 80 | } 81 | 82 | /** 83 | * 最近运行应用列表 84 | */ 85 | public static void toRecent() { 86 | AppExecutors.get().poolIO().execute(new Runnable() { 87 | @Override 88 | public void run() { 89 | sendKeyevent(KeyEvent.KEYCODE_APP_SWITCH); 90 | } 91 | }); 92 | } 93 | 94 | /** 95 | * 音量调节 96 | * 97 | * @param context 98 | * @param isAdjustLower 99 | * 是否是调低音量 100 | */ 101 | public static void volumeAdjustment(Context context, boolean isAdjustLower) { 102 | AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 103 | if (audioManager == null) return; 104 | if (isAdjustLower) { // 降低音量,调出系统音量控制 105 | audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FX_FOCUS_NAVIGATION_UP); 106 | } else { // 增加音量,调出系统音量控制 107 | audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FX_FOCUS_NAVIGATION_UP); 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/widget/MonitorView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.widget; 18 | 19 | import android.content.Context; 20 | import android.os.Handler; 21 | import android.os.Looper; 22 | import android.view.View; 23 | import android.widget.TextView; 24 | 25 | import com.xuexiang.xfloatview.XFloatView; 26 | import com.xuexiang.xfloatviewdemo.R; 27 | import com.xuexiang.xutil.app.PackageUtils; 28 | 29 | import java.text.DecimalFormat; 30 | 31 | /** 32 | * 监控悬浮控件 33 | * 34 | * @author xuexiang 35 | * @since 2018/9/13 上午11:35 36 | */ 37 | public class MonitorView extends XFloatView { 38 | 39 | private TextView mTvAppName; 40 | 41 | private TextView mTvUpLoadInfo; 42 | 43 | private TextView mTvDownLoadInfo; 44 | 45 | private DecimalFormat mSpeedFormat = new DecimalFormat("0.00"); 46 | 47 | private Handler mMainHandler = new Handler(Looper.getMainLooper()); 48 | /** 49 | * 构造器 50 | * 51 | * @param context 52 | */ 53 | public MonitorView(Context context) { 54 | super(context); 55 | } 56 | 57 | /** 58 | * 获取根布局的ID 59 | * 60 | * @return 61 | */ 62 | @Override 63 | protected int getLayoutId() { 64 | return R.layout.layout_monitor; 65 | } 66 | 67 | @Override 68 | protected boolean canMoveOrTouch() { 69 | return true; 70 | } 71 | 72 | /** 73 | * 初始化悬浮控件 74 | */ 75 | @Override 76 | protected void initFloatView() { 77 | mTvAppName = findViewById(R.id.tv_app_name); 78 | mTvUpLoadInfo = findViewById(R.id.tv_upload_speed_info); 79 | mTvDownLoadInfo = findViewById(R.id.tv_download_speed_info); 80 | } 81 | 82 | /** 83 | * 初始化监听 84 | */ 85 | @Override 86 | protected void initListener() { 87 | setOnFloatViewClickListener(new View.OnClickListener() { 88 | @Override 89 | public void onClick(View v) { 90 | PackageUtils.openApp(getContext()); 91 | } 92 | }); 93 | } 94 | 95 | @Override 96 | protected boolean isAdsorbView() { 97 | return true; 98 | } 99 | 100 | /** 101 | * 更新监测应用名 102 | * @param appName 103 | */ 104 | public void updateMonitorAppName(final String appName) { 105 | if (Looper.myLooper() == Looper.getMainLooper()) { 106 | mTvAppName.setText(String.format("应用:%s", appName)); 107 | } else { 108 | mMainHandler.post(new Runnable() { 109 | @Override 110 | public void run() { 111 | mTvAppName.setText(String.format("应用:%s", appName)); 112 | } 113 | }); 114 | } 115 | } 116 | /** 117 | * 更新网络速度 118 | * @param upLoadSpeed 上行速度 119 | * @param downLoadSpeed 下行速度 120 | */ 121 | public void updateNetWorkInfo(final double upLoadSpeed, final double downLoadSpeed) { 122 | if (Looper.myLooper() == Looper.getMainLooper()) { 123 | showSpeedInfo(upLoadSpeed, downLoadSpeed); 124 | } else { 125 | mMainHandler.post(new Runnable() { 126 | @Override 127 | public void run() { 128 | showSpeedInfo(upLoadSpeed, downLoadSpeed); 129 | } 130 | }); 131 | } 132 | } 133 | 134 | /** 135 | * 显示网络速度 136 | * @param upLoadSpeed 137 | * @param downLoadSpeed 138 | */ 139 | private void showSpeedInfo(double upLoadSpeed, double downLoadSpeed) { 140 | mTvUpLoadInfo.setText(String.format("上传:%skb/s", mSpeedFormat.format(upLoadSpeed / 1024D))); 141 | mTvDownLoadInfo.setText(String.format("下载:%skb/s", mSpeedFormat.format(downLoadSpeed / 1024D))); 142 | } 143 | 144 | 145 | @Override 146 | public void clear() { 147 | super.clear(); 148 | mMainHandler.removeCallbacksAndMessages(null); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/widget/smart/SmartIcon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.widget.smart; 18 | 19 | import android.content.Context; 20 | import android.widget.ImageView; 21 | 22 | import com.xuexiang.xfloatview.XFloatView; 23 | import com.xuexiang.xfloatviewdemo.R; 24 | 25 | /** 26 | * 悬浮图标 27 | * 28 | * @author xuexiang 29 | * @since 2018/9/13 下午2:14 30 | */ 31 | public class SmartIcon extends XFloatView { 32 | 33 | public SmartIcon(Context context) { 34 | super(context); 35 | } 36 | 37 | @Override 38 | protected int getLayoutId() { 39 | return R.layout.layout_smarticon; 40 | } 41 | 42 | @Override 43 | protected boolean canMoveOrTouch() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public void initFloatView() { 49 | setRotateView((ImageView) findViewById(R.id.ic_show), R.drawable.ic_show_normal); 50 | } 51 | 52 | @Override 53 | protected void initListener() { 54 | 55 | } 56 | 57 | @Override 58 | protected boolean isAdsorbView() { 59 | return true; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/widget/smart/SmartPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.widget.smart; 18 | 19 | import android.content.Context; 20 | import android.view.View; 21 | import android.view.View.OnClickListener; 22 | import android.widget.ImageView; 23 | 24 | import com.xuexiang.xfloatview.XFloatView; 25 | import com.xuexiang.xfloatviewdemo.R; 26 | import com.xuexiang.xfloatviewdemo.util.SystemKeyboard; 27 | import com.xuexiang.xutil.display.DensityUtils; 28 | import com.xuexiang.xutil.display.ScreenUtils; 29 | 30 | /** 31 | * 悬浮控制盘 32 | * 33 | * @author xuexiang 34 | * @since 2018/9/13 下午2:22 35 | */ 36 | public class SmartPanel extends XFloatView implements OnClickListener { 37 | 38 | private ImageView mClose; 39 | private ImageView mMore; 40 | 41 | private ImageView mBack; 42 | private ImageView mHome; 43 | private ImageView mRecent; 44 | 45 | private ImageView mMenu; 46 | private ImageView mVoiceUp; 47 | private ImageView mVoiceDown; 48 | 49 | private boolean isFirstPage = true; 50 | 51 | private onCloseListener mListener; 52 | 53 | public SmartPanel(Context context) { 54 | super(context); 55 | } 56 | 57 | @Override 58 | protected int getLayoutId() { 59 | return R.layout.layout_smartpanel; 60 | } 61 | 62 | @Override 63 | protected boolean canMoveOrTouch() { 64 | return false; 65 | } 66 | 67 | @Override 68 | public void initFloatView() { 69 | mClose = findViewById(R.id.ic_close); 70 | mMore = findViewById(R.id.ic_more); 71 | 72 | mBack = findViewById(R.id.ic_back); 73 | mHome = findViewById(R.id.ic_home); 74 | mRecent = findViewById(R.id.ic_recent); 75 | 76 | mMenu = findViewById(R.id.ic_menu); 77 | mVoiceUp = findViewById(R.id.ic_voiceup); 78 | mVoiceDown = findViewById(R.id.ic_voicedown); 79 | 80 | initFloatViewPosition(ScreenUtils.getScreenWidth() - DensityUtils.dip2px(55), (ScreenUtils.getScreenHeight() - getFloatRootView().getMeasuredHeight()) / 2 - getStatusBarHeight()); 81 | } 82 | 83 | @Override 84 | protected void initListener() { 85 | mClose.setOnClickListener(this); 86 | mMore.setOnClickListener(this); 87 | 88 | mBack.setOnClickListener(this); 89 | mHome.setOnClickListener(this); 90 | mRecent.setOnClickListener(this); 91 | 92 | mMenu.setOnClickListener(this); 93 | mVoiceUp.setOnClickListener(this); 94 | mVoiceDown.setOnClickListener(this); 95 | } 96 | 97 | @Override 98 | protected boolean isAdsorbView() { 99 | return false; 100 | } 101 | 102 | @Override 103 | public void onClick(View v) { 104 | switch (v.getId()) { 105 | case R.id.ic_close: 106 | if (mListener != null) { 107 | mListener.onClose(); 108 | } 109 | break; 110 | case R.id.ic_more: 111 | if (isFirstPage) { 112 | mHome.setVisibility(View.GONE); 113 | mBack.setVisibility(View.GONE); 114 | mRecent.setVisibility(View.GONE); 115 | 116 | mMenu.setVisibility(View.VISIBLE); 117 | mVoiceUp.setVisibility(View.VISIBLE); 118 | mVoiceDown.setVisibility(View.VISIBLE); 119 | isFirstPage = false; 120 | } else { 121 | mHome.setVisibility(View.VISIBLE); 122 | mBack.setVisibility(View.VISIBLE); 123 | mRecent.setVisibility(View.VISIBLE); 124 | 125 | mMenu.setVisibility(View.GONE); 126 | mVoiceUp.setVisibility(View.GONE); 127 | mVoiceDown.setVisibility(View.GONE); 128 | isFirstPage = true; 129 | } 130 | break; 131 | case R.id.ic_back: 132 | SystemKeyboard.toBack(); 133 | break; 134 | case R.id.ic_home: 135 | SystemKeyboard.toHome(getContext()); 136 | break; 137 | case R.id.ic_recent: 138 | SystemKeyboard.toRecent(); 139 | break; 140 | case R.id.ic_menu: 141 | SystemKeyboard.toMenu(); 142 | break; 143 | case R.id.ic_voiceup: 144 | SystemKeyboard.volumeAdjustment(getContext(), false); 145 | break; 146 | case R.id.ic_voicedown: 147 | SystemKeyboard.volumeAdjustment(getContext(), true); 148 | break; 149 | 150 | default: 151 | break; 152 | } 153 | } 154 | 155 | public void setOnCloseListener(onCloseListener listener) { 156 | mListener = listener; 157 | } 158 | 159 | /** 160 | * 关闭的监听 161 | */ 162 | public interface onCloseListener { 163 | void onClose(); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/xfloatviewdemo/widget/smart/SmartView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatviewdemo.widget.smart; 18 | 19 | import android.content.Context; 20 | import android.view.View; 21 | import android.view.View.OnClickListener; 22 | 23 | /** 24 | * 按钮控制盘控件 25 | * 26 | * @author xuexiang 27 | * @since 2018/9/13 下午2:26 28 | */ 29 | public class SmartView { 30 | /** 31 | * 悬浮图标 32 | */ 33 | private SmartIcon mSmartIcon; 34 | /** 35 | * 悬浮控制盘 36 | */ 37 | private SmartPanel mSmartPanel; 38 | private Context mContext; 39 | 40 | public SmartView(Context context) { 41 | mContext = context; 42 | initView(); 43 | } 44 | 45 | public void initView() { 46 | mSmartIcon = new SmartIcon(mContext); 47 | mSmartPanel = new SmartPanel(mContext); 48 | mSmartIcon.setOnFloatViewClickListener(new OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | mSmartPanel.show(); 52 | mSmartIcon.dismiss(); 53 | } 54 | }); 55 | mSmartPanel.setOnCloseListener(new SmartPanel.onCloseListener() { 56 | @Override 57 | public void onClose() { 58 | mSmartPanel.dismiss(); 59 | mSmartIcon.show(); 60 | } 61 | }); 62 | mSmartIcon.show(); 63 | } 64 | 65 | public void refreshSmartView() { 66 | if (mSmartIcon != null) { 67 | mSmartIcon.clear(); 68 | } 69 | if (mSmartPanel != null) { 70 | mSmartPanel.clear(); 71 | } 72 | initView(); 73 | } 74 | 75 | public void destroy() { 76 | if (mSmartIcon != null) { 77 | mSmartIcon.clear(); 78 | mSmartIcon = null; 79 | } 80 | if (mSmartPanel != null) { 81 | mSmartPanel.clear(); 82 | mSmartPanel = null; 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_back_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_back_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_back_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_back_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_close_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_close_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_close_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_close_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_home_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_home_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_home_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_menu_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_menu_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_more_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_more_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_more_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_more_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_recent_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_recent_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_recent_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_recent_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_show.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_show_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_show_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_show_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_show_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_voicedown_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_voicedown_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_voicedown_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_voicedown_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_voiceup_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_voiceup_click.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_voiceup_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/drawable-hdpi/ic_voiceup_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_smart_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_recent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_voice_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_ic_voice_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_app_traffic.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_recycler_app_traffic_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 25 | 26 | 35 | 36 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_monitor.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_smarticon.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_smartpanel.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 25 | 30 | 31 | 37 | 38 | 44 | 45 | 51 | 52 | 58 | 59 | 66 | 67 | 74 | 75 | 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #299EE3 4 | #299EE3 5 | #299EE3 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | XFloatViewDemo 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/xuexiang/xfloatview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.xfloatview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | apply from: './versions.gradle' 4 | addRepos(repositories) //增加代码仓库 5 | dependencies { 6 | classpath deps.android_gradle_plugin 7 | classpath deps.android_maven_gradle_plugin 8 | } 9 | } 10 | 11 | allprojects { 12 | addRepos(repositories) 13 | } 14 | 15 | task clean(type: Delete) { 16 | delete rootProject.buildDir 17 | } 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | # 是否打包APK 16 | isNeedPackage=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 16 10:07:47 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/img/demo.gif -------------------------------------------------------------------------------- /img/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/XFloatView/27e577fe6b9097af52555a9aa6cdd79ad2b21a66/img/download.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':xfloatview-lib' 2 | -------------------------------------------------------------------------------- /versions.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared file between builds so that they can all use the same dependencies and 3 | * maven repositories. 4 | **/ 5 | ext.deps = [:] 6 | def versions = [:] 7 | versions.android_gradle_plugin = '3.4.1' 8 | versions.android_maven_gradle_plugin = "2.0" 9 | versions.gradle_bintray_plugin = "1.8.0" 10 | versions.arch_core = "1.1.0" 11 | versions.room = "1.1.0-alpha1" 12 | versions.lifecycle = "1.1.0" 13 | versions.support = "28.0.0" 14 | versions.dagger = "2.11" 15 | versions.junit = "4.12" 16 | versions.espresso = "3.0.2" 17 | versions.retrofit = "2.3.0" 18 | versions.okhttp_logging_interceptor = "3.9.0" 19 | versions.mockwebserver = "3.8.1" 20 | versions.apache_commons = "2.5" 21 | versions.mockito = "2.7.19" 22 | versions.mockito_all = "1.10.19" 23 | versions.dexmaker = "2.2.0" 24 | versions.constraint_layout = "1.0.2" 25 | versions.glide = "3.8.0" 26 | versions.timber = "4.5.1" 27 | versions.rxjava2 = "2.1.3" 28 | versions.rx_android = "2.0.1" 29 | versions.atsl_runner = "1.0.1" 30 | versions.atsl_rules = "1.0.1" 31 | versions.hamcrest = "1.3" 32 | versions.kotlin = "1.2.20" 33 | versions.paging = "1.0.0-alpha5" 34 | versions.butterknife = "8.8.1" 35 | versions.runner = "1.0.1" 36 | versions.gson = "2.8.2" 37 | versions.okhttp3 = "3.9.1" 38 | versions.nineoldandroids = "2.4.0" 39 | versions.calligraphy = "2.3.0" 40 | versions.godeye = "1.7.4" 41 | 42 | def deps = [:] 43 | 44 | def support = [:] 45 | support.annotations = "com.android.support:support-annotations:$versions.support" 46 | support.app_compat = "com.android.support:appcompat-v7:$versions.support" 47 | support.recyclerview = "com.android.support:recyclerview-v7:$versions.support" 48 | support.cardview = "com.android.support:cardview-v7:$versions.support" 49 | support.design = "com.android.support:design:$versions.support" 50 | support.v4 = "com.android.support:support-v4:$versions.support" 51 | support.core_utils = "com.android.support:support-core-utils:$versions.support" 52 | deps.support = support 53 | 54 | def room = [:] 55 | room.runtime = "android.arch.persistence.room:runtime:$versions.room" 56 | room.compiler = "android.arch.persistence.room:compiler:$versions.room" 57 | room.rxjava2 = "android.arch.persistence.room:rxjava2:$versions.room" 58 | room.testing = "android.arch.persistence.room:testing:$versions.room" 59 | deps.room = room 60 | 61 | def lifecycle = [:] 62 | lifecycle.runtime = "android.arch.lifecycle:runtime:$versions.lifecycle" 63 | lifecycle.extensions = "android.arch.lifecycle:extensions:$versions.lifecycle" 64 | lifecycle.java8 = "android.arch.lifecycle:common-java8:$versions.lifecycle" 65 | lifecycle.compiler = "android.arch.lifecycle:compiler:$versions.lifecycle" 66 | deps.lifecycle = lifecycle 67 | 68 | def arch_core = [:] 69 | arch_core.testing = "android.arch.core:core-testing:$versions.arch_core" 70 | deps.arch_core = arch_core 71 | 72 | def retrofit = [:] 73 | retrofit.runtime = "com.squareup.retrofit2:retrofit:$versions.retrofit" 74 | retrofit.gson = "com.squareup.retrofit2:converter-gson:$versions.retrofit" 75 | retrofit.mock = "com.squareup.retrofit2:retrofit-mock:$versions.retrofit" 76 | deps.retrofit = retrofit 77 | deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:${versions.okhttp_logging_interceptor}" 78 | 79 | def dagger = [:] 80 | dagger.runtime = "com.google.dagger:dagger:$versions.dagger" 81 | dagger.android = "com.google.dagger:dagger-android:$versions.dagger" 82 | dagger.android_support = "com.google.dagger:dagger-android-support:$versions.dagger" 83 | dagger.compiler = "com.google.dagger:dagger-compiler:$versions.dagger" 84 | dagger.android_support_compiler = "com.google.dagger:dagger-android-processor:$versions.dagger" 85 | 86 | deps.dagger = dagger 87 | 88 | def butterknife = [:] 89 | butterknife.runtime = "com.jakewharton:butterknife:$versions.butterknife" 90 | butterknife.compiler = "com.jakewharton:butterknife-compiler:$versions.butterknife" 91 | 92 | deps.butterknife = butterknife 93 | 94 | def arouter = [:] 95 | arouter.api = "com.alibaba:arouter-api:1.3.1" 96 | arouter.compiler = "com.alibaba:arouter-compiler:1.1.4" 97 | 98 | deps.arouter = arouter 99 | 100 | def espresso = [:] 101 | espresso.core = "com.android.support.test.espresso:espresso-core:$versions.espresso" 102 | espresso.contrib = "com.android.support.test.espresso:espresso-contrib:$versions.espresso" 103 | espresso.intents = "com.android.support.test.espresso:espresso-intents:$versions.espresso" 104 | deps.espresso = espresso 105 | 106 | def atsl = [:] 107 | atsl.runner = "com.android.support.test:runner:$versions.atsl_runner" 108 | atsl.rules = "com.android.support.test:rules:$versions.atsl_runner" 109 | deps.atsl = atsl 110 | 111 | def mockito = [:] 112 | mockito.core = "org.mockito:mockito-core:$versions.mockito" 113 | mockito.all = "org.mockito:mockito-all:$versions.mockito_all" 114 | deps.mockito = mockito 115 | 116 | def kotlin = [:] 117 | kotlin.stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jre7:$versions.kotlin" 118 | kotlin.test = "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin" 119 | kotlin.plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" 120 | 121 | deps.kotlin = kotlin 122 | 123 | def godeye = [:] 124 | godeye.core = "cn.hikyson.godeye:godeye-core:$versions.godeye" 125 | godeye.toolbox = "cn.hikyson.godeye:godeye-toolbox:$versions.godeye" 126 | godeye.monitor = "cn.hikyson.godeye:godeye-monitor:$versions.godeye" 127 | godeye.monitor_no_op = "cn.hikyson.godeye:godeye-monitor-no-op:$versions.godeye" 128 | 129 | deps.godeye = godeye 130 | 131 | deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin" 132 | deps.android_maven_gradle_plugin = "com.github.dcendents:android-maven-gradle-plugin:$versions.android_maven_gradle_plugin" 133 | deps.gradle_bintray_plugin = "com.jfrog.bintray.gradle:gradle-bintray-plugin:$versions.gradle_bintray_plugin" 134 | deps.freeline_gradle_plugin = "com.antfortune.freeline:gradle:0.8.8" 135 | deps.paging = "android.arch.paging:runtime:$versions.paging" 136 | deps.glide = "com.github.bumptech.glide:glide:$versions.glide" 137 | deps.dexmaker = "com.linkedin.dexmaker:dexmaker-mockito:$versions.dexmaker" 138 | deps.constraint_layout = "com.android.support.constraint:constraint-layout:$versions.constraint_layout" 139 | deps.timber = "com.jakewharton.timber:timber:$versions.timber" 140 | deps.junit = "junit:junit:$versions.junit" 141 | deps.runner = "com.android.support.test:runner:$versions.runner" 142 | deps.mock_web_server = "com.squareup.okhttp3:mockwebserver:$versions.mockwebserver" 143 | deps.rxjava2 = "io.reactivex.rxjava2:rxjava:$versions.rxjava2" 144 | deps.rx_android = "io.reactivex.rxjava2:rxandroid:$versions.rx_android" 145 | deps.hamcrest = "org.hamcrest:hamcrest-all:$versions.hamcrest" 146 | deps.gson = "com.google.code.gson:gson:$versions.gson" 147 | deps.okhttp3 = "com.squareup.okhttp3:okhttp:$versions.okhttp3" 148 | deps.nineoldandroids = "com.nineoldandroids:library:$versions.nineoldandroids" 149 | deps.calligraphy = "uk.co.chrisjenx:calligraphy:$versions.calligraphy" 150 | 151 | ext.deps = deps 152 | 153 | def build_versions = [:] 154 | build_versions.min_sdk = 19 155 | build_versions.target_sdk = 28 156 | build_versions.build_tools = "28.0.3" 157 | ext.build_versions = build_versions 158 | 159 | def app_release = [:] 160 | app_release.storeFile = "../keystores/android.keystore" 161 | app_release.storePassword = "xuexiang" 162 | app_release.keyAlias = "android.keystore" 163 | app_release.keyPassword = "xuexiang" 164 | 165 | ext.app_release = app_release 166 | 167 | //默认添加代码仓库路径 168 | def addRepos(RepositoryHandler handler) { 169 | handler.google() 170 | handler.jcenter() 171 | handler.mavenCentral() 172 | //Add the JitPack repository 173 | handler.maven { url "https://jitpack.io" } 174 | //Add the aliyun repository 175 | handler.maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} 176 | //Add the Local repository 177 | handler.maven{ url 'LocalRepository'} 178 | } 179 | 180 | ext.addRepos = this.&addRepos 181 | 182 | 183 | -------------------------------------------------------------------------------- /xfloatview-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /xfloatview-lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion build_versions.target_sdk 5 | buildToolsVersion build_versions.build_tools 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion build_versions.target_sdk 10 | } 11 | 12 | lintOptions { 13 | abortOnError false 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation fileTree(dir: 'libs', include: ['*.jar']) 19 | compileOnly deps.support.app_compat 20 | } 21 | 22 | apply from: "../JitPackUpload.gradle" -------------------------------------------------------------------------------- /xfloatview-lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatview; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Bitmap; 22 | import android.graphics.Canvas; 23 | import android.graphics.Matrix; 24 | import android.graphics.PixelFormat; 25 | import android.graphics.drawable.Drawable; 26 | 27 | /** 28 | * 工具类 29 | * 30 | * @author xuexiang 31 | * @since 2018/9/13 上午2:15 32 | */ 33 | final class Utils { 34 | 35 | private Utils() { 36 | throw new UnsupportedOperationException("u can't instantiate me..."); 37 | } 38 | 39 | /** 40 | * 得到设备屏幕的高度 41 | */ 42 | static int getScreenHeight(Context context) { 43 | return context.getResources().getDisplayMetrics().heightPixels; 44 | } 45 | 46 | /** 47 | * 得到设备屏幕的宽度 48 | */ 49 | static int getScreenWidth(Context context) { 50 | return context.getResources().getDisplayMetrics().widthPixels; 51 | } 52 | 53 | static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height"; 54 | 55 | /** 56 | * 计算状态栏高度高度 getStatusBarHeight 57 | * 58 | * @return 59 | */ 60 | static int getStatusBarHeight() { 61 | return getInternalDimensionSize(Resources.getSystem(), 62 | STATUS_BAR_HEIGHT_RES_NAME); 63 | } 64 | 65 | static int getInternalDimensionSize(Resources res, String key) { 66 | int result = 0; 67 | int resourceId = res.getIdentifier(key, "dimen", "android"); 68 | if (resourceId > 0) { 69 | result = res.getDimensionPixelSize(resourceId); 70 | } 71 | return result; 72 | } 73 | 74 | /** 75 | * 旋转图片 76 | * 77 | * @param angle 旋转角度 78 | * @param bitmap 要旋转的图片 79 | * @return 旋转后的图片 80 | */ 81 | static Bitmap rotate(Bitmap bitmap, int angle) { 82 | Matrix matrix = new Matrix(); 83 | matrix.postRotate(angle); 84 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), 85 | bitmap.getHeight(), matrix, true); 86 | } 87 | 88 | /** 89 | * 将Drawable转化为Bitmap 90 | * 91 | * @param drawable Drawable 92 | * @return Bitmap 93 | */ 94 | static Bitmap drawable2Bitmap(Drawable drawable) { 95 | int width = drawable.getIntrinsicWidth(); 96 | int height = drawable.getIntrinsicHeight(); 97 | Bitmap bitmap = Bitmap.createBitmap(width, height, drawable 98 | .getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 99 | : Bitmap.Config.RGB_565); 100 | Canvas canvas = new Canvas(bitmap); 101 | drawable.setBounds(0, 0, width, height); 102 | drawable.draw(canvas); 103 | return bitmap; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/XFloatView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatview; 18 | 19 | import android.content.Context; 20 | import android.graphics.Bitmap; 21 | import android.graphics.PixelFormat; 22 | import android.os.Build; 23 | import android.view.Gravity; 24 | import android.view.LayoutInflater; 25 | import android.view.MotionEvent; 26 | import android.view.View; 27 | import android.view.View.OnClickListener; 28 | import android.view.View.OnTouchListener; 29 | import android.view.WindowManager; 30 | import android.view.WindowManager.LayoutParams; 31 | import android.widget.ImageView; 32 | 33 | /** 34 | * 悬浮窗基类,实现悬浮窗只需继承该类即可 35 | *

36 | *

需添加权限 37 | * {@code }

38 | * 39 | * @author xuexiang 40 | * @since 2018/9/13 上午2:19 41 | */ 42 | public abstract class XFloatView implements OnTouchListener { 43 | 44 | private Context mContext; 45 | /** 46 | * 悬浮窗的布局参数 47 | */ 48 | private LayoutParams mWmParams; 49 | /** 50 | * 创建浮动窗口设置布局参数的对象 51 | */ 52 | private WindowManager mWindowManager; 53 | /** 54 | * 浮动窗口的父布局 55 | */ 56 | private View mFloatRootView; 57 | /** 58 | * 系统状态栏的高度 59 | */ 60 | private int mStatusBarHeight; 61 | 62 | private Location mLocation; 63 | private OnClickListener mOnClickListener; 64 | private OnFloatViewMoveListener mOnFloatViewMoveListener; 65 | /** 66 | * 吸附旋转的控件 67 | */ 68 | private ImageView mRotateView; 69 | private Bitmap mBitmap; 70 | /** 71 | * 悬浮窗口是否显示 72 | */ 73 | private boolean mIsShow; 74 | 75 | /** 76 | * 构造器 77 | */ 78 | public XFloatView(Context context) { 79 | init(context); 80 | 81 | initFloatRootView(getLayoutId()); 82 | 83 | initFloatViewPosition(); 84 | 85 | initFloatView(); 86 | 87 | initListener(); 88 | } 89 | 90 | /** 91 | * @return 获取根布局的ID 92 | */ 93 | protected abstract int getLayoutId(); 94 | 95 | /** 96 | * @return 能否移动或者触摸响应 97 | */ 98 | protected abstract boolean canMoveOrTouch(); 99 | 100 | /** 101 | * 初始化悬浮控件 102 | */ 103 | protected abstract void initFloatView(); 104 | 105 | /** 106 | * 初始化监听 107 | */ 108 | protected abstract void initListener(); 109 | 110 | /** 111 | * @return 设置悬浮框是否吸附在屏幕边缘 112 | */ 113 | protected abstract boolean isAdsorbView(); 114 | 115 | /** 116 | * 初始化悬浮窗Window 117 | * 118 | * @param context 119 | */ 120 | private void init(Context context) { 121 | mContext = context; 122 | initFloatViewLayoutParams(); 123 | 124 | setWindowManagerParams(0, 0, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 125 | } 126 | 127 | /** 128 | * 初始化悬浮窗的LayoutParams 129 | */ 130 | private void initFloatViewLayoutParams() { 131 | // 获取WindowManagerImpl.CompatModeWrapper 132 | mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 133 | mWmParams = getFloatViewLayoutParams(); 134 | mStatusBarHeight = Utils.getStatusBarHeight(); 135 | } 136 | 137 | /** 138 | * 设置悬浮窗的LayoutParams(可重写) 139 | * 140 | * @return 悬浮窗的LayoutParams 141 | */ 142 | protected LayoutParams getFloatViewLayoutParams() { 143 | LayoutParams params = new LayoutParams(); 144 | // 设置window type 145 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 146 | params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 147 | } else { 148 | params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; 149 | } 150 | // 设置图片格式,效果为背景透明 151 | params.format = PixelFormat.RGBA_8888; 152 | // 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) 153 | params.flags = 154 | // LayoutParams.FLAG_NOT_TOUCH_MODAL | 155 | LayoutParams.FLAG_NOT_FOCUSABLE 156 | // LayoutParams.FLAG_NOT_TOUCHABLE 157 | ; 158 | // 调整悬浮窗显示的停靠位置为左侧置顶 159 | params.gravity = Gravity.LEFT | Gravity.TOP; 160 | return params; 161 | } 162 | 163 | /** 164 | * 设置悬浮框的初始位置、尺寸参数 165 | * 166 | * @param PosX 167 | * @param PosY 168 | * @param width 169 | * @param height 170 | */ 171 | public void setWindowManagerParams(int PosX, int PosY, int width, int height) { 172 | mWmParams.x = PosX; 173 | mWmParams.y = PosY; 174 | // 设置悬浮窗口长宽数据 175 | mWmParams.width = width; 176 | mWmParams.height = height; 177 | } 178 | 179 | /** 180 | * 初始化父布局 181 | * 182 | * @param layoutId 布局的资源ID(最好是LinearLayout) 183 | * @return 184 | */ 185 | private View initFloatRootView(int layoutId) { 186 | //获取浮动窗口视图所在布局 187 | mFloatRootView = LayoutInflater.from(mContext).inflate(layoutId, null); 188 | mFloatRootView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 189 | if (canMoveOrTouch()) { 190 | mFloatRootView.setOnTouchListener(this); 191 | } 192 | return mFloatRootView; 193 | } 194 | 195 | /** 196 | * 设置悬浮框的初始位置【屏幕最右边,中间的位置】 197 | */ 198 | public void initFloatViewPosition() { 199 | int x = Utils.getScreenWidth(getContext()); 200 | int y = (Utils.getScreenHeight(getContext()) - mFloatRootView.getMeasuredHeight()) / 2 - mStatusBarHeight; 201 | initFloatViewPosition(x, y); 202 | } 203 | 204 | /** 205 | * 设置悬浮框的初始位置 206 | */ 207 | public void initFloatViewPosition(int PosX, int PosY) { 208 | mWmParams.x = PosX; 209 | mWmParams.y = PosY; 210 | mLocation = new Location(); 211 | } 212 | 213 | /** 214 | * 更新悬浮框的位置参数 215 | * 216 | * @param PosX 217 | * @param PosY 218 | */ 219 | public void updateViewPosition(int PosX, int PosY) { 220 | mWmParams.x = PosX; 221 | mWmParams.y = PosY; 222 | mWindowManager.updateViewLayout(mFloatRootView, mWmParams); 223 | } 224 | 225 | //==========================show/dismiss=============================// 226 | 227 | /** 228 | * 显示悬浮框 229 | */ 230 | public void show() { 231 | if (mFloatRootView != null && mWmParams != null && !mIsShow) { 232 | mWindowManager.addView(mFloatRootView, mWmParams); 233 | mIsShow = true; 234 | } 235 | } 236 | 237 | /** 238 | * 隐藏悬浮框 239 | */ 240 | public void dismiss() { 241 | if (mFloatRootView != null && mIsShow) { 242 | mWindowManager.removeView(mFloatRootView); 243 | mIsShow = false; 244 | } 245 | } 246 | 247 | /** 248 | * 销毁悬浮框 249 | */ 250 | public void clear() { 251 | dismiss(); 252 | if (mRotateView != null) { 253 | mRotateView = null; 254 | if (mBitmap != null) { 255 | mBitmap.recycle(); 256 | mBitmap = null; 257 | } 258 | } 259 | if (mFloatRootView != null) { 260 | mFloatRootView = null; 261 | mWmParams = null; 262 | mWindowManager = null; 263 | mLocation = null; 264 | } 265 | } 266 | 267 | //==========================api=============================// 268 | 269 | /** 270 | * 设置悬浮窗的点击监听 271 | * 272 | * @param onClickListener 273 | * @return 274 | */ 275 | public XFloatView setOnFloatViewClickListener(OnClickListener onClickListener) { 276 | mOnClickListener = onClickListener; 277 | return this; 278 | } 279 | 280 | /** 281 | * 设置悬浮窗的移动监听 282 | * 283 | * @param onFloatViewMoveListener 284 | * @return 285 | */ 286 | public XFloatView setOnFloatViewMoveListener(OnFloatViewMoveListener onFloatViewMoveListener) { 287 | mOnFloatViewMoveListener = onFloatViewMoveListener; 288 | return this; 289 | } 290 | 291 | /** 292 | * 设置需要旋转的ImageView控件 293 | * 294 | * @param rotateView 295 | * @param resId 旋转图片资源的id 296 | */ 297 | public void setRotateView(ImageView rotateView, int resId) { 298 | mRotateView = rotateView; 299 | mBitmap = Utils.drawable2Bitmap(mContext.getResources().getDrawable(resId)); 300 | mRotateView.setImageBitmap(mBitmap); 301 | } 302 | 303 | //=======================触摸事件===========================// 304 | 305 | @Override 306 | public boolean onTouch(View v, MotionEvent event) { 307 | switch (event.getAction()) { 308 | // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度 309 | case MotionEvent.ACTION_DOWN: 310 | // 获取相对与小悬浮窗的坐标 311 | mLocation.mXInView = event.getX(); 312 | mLocation.mYInView = event.getY(); 313 | // 按下时的坐标位置,只记录一次 314 | mLocation.mXDownInScreen = event.getRawX(); 315 | mLocation.mYDownInScreen = event.getRawY() - mStatusBarHeight; 316 | break; 317 | case MotionEvent.ACTION_MOVE: 318 | // 时时的更新当前手指在屏幕上的位置 319 | mLocation.mXInScreen = event.getRawX(); 320 | mLocation.mYInScreen = event.getRawY() - mStatusBarHeight; 321 | // 手指移动的时候更新小悬浮窗的位置 322 | if (mOnFloatViewMoveListener != null) { 323 | mOnFloatViewMoveListener.onMove(mLocation); 324 | } else { 325 | updateViewPosition((int) (mLocation.mXInScreen - mLocation.mXInView), (int) (mLocation.mYInScreen - mLocation.mYInView)); 326 | } 327 | break; 328 | case MotionEvent.ACTION_UP: 329 | // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件 330 | if (Math.abs(mLocation.getXDownInScreen() - event.getRawX()) < 10 && Math.abs(mLocation.getYDownInScreen() - (event.getRawY() - mStatusBarHeight)) < 10) { 331 | if (mOnClickListener != null) { 332 | mOnClickListener.onClick(v); 333 | } 334 | } else { 335 | if (isAdsorbView()) { 336 | updateGravity(event); 337 | } 338 | } 339 | break; 340 | } 341 | return true; 342 | } 343 | 344 | /** 345 | * 获取控件的位置类型 346 | * 347 | * @param event 348 | * @return 349 | */ 350 | private PositionType getPositionType(MotionEvent event) { 351 | PositionType type; 352 | int height = Utils.getScreenHeight(getContext()) / 5; 353 | int width = Utils.getScreenWidth(getContext()) / 2; 354 | if (event.getRawY() < height) { 355 | type = PositionType.TOP; 356 | } else if (event.getRawY() > (height * 4)) { 357 | type = PositionType.BOTTOM; 358 | } else { 359 | if (event.getRawX() > width) { 360 | type = PositionType.RIGHT; 361 | } else { 362 | type = PositionType.LEFT; 363 | } 364 | } 365 | return type; 366 | } 367 | 368 | /** 369 | * 更新重心 370 | * 371 | * @param event 372 | */ 373 | private void updateGravity(MotionEvent event) { 374 | PositionType type = getPositionType(event); 375 | switch (type) { 376 | case TOP: 377 | updateRotateView(-90); 378 | updateViewPosition((int) (event.getRawX() - event.getX()), 0); 379 | break; 380 | case BOTTOM: 381 | updateRotateView(90); 382 | updateViewPosition((int) (event.getRawX() - event.getX()), Utils.getScreenHeight(getContext())); 383 | break; 384 | case RIGHT: 385 | updateRotateView(0); 386 | updateViewPosition(Utils.getScreenWidth(getContext()), (int) (event.getRawY() - event.getY()) - mStatusBarHeight); 387 | break; 388 | case LEFT: 389 | updateRotateView(180); 390 | updateViewPosition(0, (int) (event.getRawY() - event.getY()) - mStatusBarHeight); 391 | break; 392 | default: 393 | break; 394 | } 395 | } 396 | 397 | /** 398 | * 旋转悬浮框图标 399 | * 400 | * @param degree 角度 401 | */ 402 | private void updateRotateView(int degree) { 403 | if (mRotateView != null) { 404 | if (degree != 0) { 405 | mRotateView.setImageBitmap(Utils.rotate(mBitmap, degree)); 406 | } else { 407 | mRotateView.setImageBitmap(mBitmap); 408 | } 409 | } 410 | } 411 | 412 | //==========================get=============================// 413 | 414 | public Context getContext() { 415 | return mContext; 416 | } 417 | 418 | public T findViewById(int resId) { 419 | return mFloatRootView != null ? (T) mFloatRootView.findViewById(resId) : null; 420 | } 421 | 422 | public View getFloatRootView() { 423 | return mFloatRootView; 424 | } 425 | 426 | public LayoutParams getWmParams() { 427 | return mWmParams; 428 | } 429 | 430 | public void setWmParams(LayoutParams wmParams) { 431 | this.mWmParams = wmParams; 432 | } 433 | 434 | public WindowManager getWindowManager() { 435 | return mWindowManager; 436 | } 437 | 438 | public int getStatusBarHeight() { 439 | return mStatusBarHeight; 440 | } 441 | 442 | //============================================================// 443 | 444 | /** 445 | * 悬浮框移动监听 446 | * 447 | * @author xx 448 | */ 449 | public interface OnFloatViewMoveListener { 450 | /** 451 | * 移动 452 | * 453 | * @param location 位置信息 454 | */ 455 | void onMove(Location location); 456 | } 457 | 458 | /** 459 | * 控件位置类型 460 | * 461 | * @author xx 462 | */ 463 | public enum PositionType { 464 | LEFT, RIGHT, TOP, BOTTOM 465 | } 466 | 467 | /** 468 | * 悬浮控件的位置信息 469 | * 470 | * @author xuexiang 471 | * @since 2018/9/13 上午2:22 472 | */ 473 | public final class Location { 474 | /** 475 | * 记录当前手指位置在屏幕上的横坐标 476 | */ 477 | private float mXInScreen; 478 | /** 479 | * 记录当前手指位置在屏幕上的纵坐标 480 | */ 481 | private float mYInScreen; 482 | /** 483 | * 记录手指按下时在屏幕上的横坐标,用来判断单击事件 484 | */ 485 | private float mXDownInScreen; 486 | /** 487 | * 记录手指按下时在屏幕上的纵坐标,用来判断单击事件 488 | */ 489 | private float mYDownInScreen; 490 | /** 491 | * 记录手指按下时在小悬浮窗的View上的横坐标 492 | */ 493 | private float mXInView; 494 | /** 495 | * 记录手指按下时在小悬浮窗的View上的纵坐标 496 | */ 497 | private float mYInView; 498 | 499 | public float getXInScreen() { 500 | return mXInScreen; 501 | } 502 | 503 | public void setXInScreen(float xInScreen) { 504 | mXInScreen = xInScreen; 505 | } 506 | 507 | public float getYInScreen() { 508 | return mYInScreen; 509 | } 510 | 511 | public void setYInScreen(float yInScreen) { 512 | mYInScreen = yInScreen; 513 | } 514 | 515 | public float getXDownInScreen() { 516 | return mXDownInScreen; 517 | } 518 | 519 | public void setXDownInScreen(float xDownInScreen) { 520 | mXDownInScreen = xDownInScreen; 521 | } 522 | 523 | public float getYDownInScreen() { 524 | return mYDownInScreen; 525 | } 526 | 527 | public void setYDownInScreen(float yDownInScreen) { 528 | mYDownInScreen = yDownInScreen; 529 | } 530 | 531 | public float getXInView() { 532 | return mXInView; 533 | } 534 | 535 | public void setXInView(float xInView) { 536 | mXInView = xInView; 537 | } 538 | 539 | public float getYInView() { 540 | return mYInView; 541 | } 542 | 543 | public void setYInView(float yInView) { 544 | mYInView = yInView; 545 | } 546 | } 547 | 548 | } 549 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/FloatWindowPermission.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission; 17 | 18 | import android.app.Activity; 19 | import android.app.AlertDialog; 20 | import android.app.Dialog; 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.content.Intent; 24 | import android.net.Uri; 25 | import android.os.Build; 26 | import android.provider.Settings; 27 | import android.support.annotation.NonNull; 28 | import android.util.Log; 29 | 30 | import com.xuexiang.xfloatview.permission.rom.HuaweiUtils; 31 | import com.xuexiang.xfloatview.permission.rom.MeizuUtils; 32 | import com.xuexiang.xfloatview.permission.rom.MiuiUtils; 33 | import com.xuexiang.xfloatview.permission.rom.OppoUtils; 34 | import com.xuexiang.xfloatview.permission.rom.QikuUtils; 35 | import com.xuexiang.xfloatview.permission.rom.RomUtils; 36 | 37 | import java.lang.reflect.Method; 38 | 39 | /** 40 | * 悬浮窗权限申请 41 | * 42 | * @author xuexiang 43 | * @since 2018/10/17 上午10:46 44 | */ 45 | public class FloatWindowPermission { 46 | private static final String TAG = "FloatWindowPermission"; 47 | public static final int REQUEST_OVERLAY_PERMISSION_CODE = 10001; 48 | 49 | private static volatile FloatWindowPermission sInstance; 50 | 51 | private IPermissionApplyPrompter mIPermissionApplyPrompter; 52 | 53 | private FloatWindowPermission() { 54 | mIPermissionApplyPrompter = new DefaultPermissionApplyPrompter(); 55 | } 56 | 57 | public static FloatWindowPermission getInstance() { 58 | if (sInstance == null) { 59 | synchronized (FloatWindowPermission.class) { 60 | if (sInstance == null) { 61 | sInstance = new FloatWindowPermission(); 62 | } 63 | } 64 | } 65 | return sInstance; 66 | } 67 | 68 | public Dialog applyFloatWindowPermission(Context context) { 69 | if (!checkPermission(context)) { 70 | return applyPermission(context); 71 | } 72 | return null; 73 | } 74 | 75 | private boolean checkPermission(Context context) { 76 | //6.0 版本之后由于 google 增加了对悬浮窗权限的管理,所以方式就统一了 77 | if (Build.VERSION.SDK_INT < 23) { 78 | if (RomUtils.checkIsMiuiRom()) { 79 | return miuiPermissionCheck(context); 80 | } else if (RomUtils.checkIsMeizuRom()) { 81 | return meizuPermissionCheck(context); 82 | } else if (RomUtils.checkIsHuaweiRom()) { 83 | return huaweiPermissionCheck(context); 84 | } else if (RomUtils.checkIs360Rom()) { 85 | return qikuPermissionCheck(context); 86 | } else if (RomUtils.checkIsOppoRom()) { 87 | return oppoROMPermissionCheck(context); 88 | } 89 | } 90 | return commonROMPermissionCheck(context); 91 | } 92 | 93 | private boolean huaweiPermissionCheck(Context context) { 94 | return HuaweiUtils.checkFloatWindowPermission(context); 95 | } 96 | 97 | private boolean miuiPermissionCheck(Context context) { 98 | return MiuiUtils.checkFloatWindowPermission(context); 99 | } 100 | 101 | private boolean meizuPermissionCheck(Context context) { 102 | return MeizuUtils.checkFloatWindowPermission(context); 103 | } 104 | 105 | private boolean qikuPermissionCheck(Context context) { 106 | return QikuUtils.checkFloatWindowPermission(context); 107 | } 108 | 109 | private boolean oppoROMPermissionCheck(Context context) { 110 | return OppoUtils.checkFloatWindowPermission(context); 111 | } 112 | 113 | private boolean commonROMPermissionCheck(Context context) { 114 | //最新发现魅族6.0的系统这种方式不好用,天杀的,只有你是奇葩,没办法,单独适配一下 115 | if (RomUtils.checkIsMeizuRom()) { 116 | return meizuPermissionCheck(context); 117 | } else { 118 | Boolean result = true; 119 | if (Build.VERSION.SDK_INT >= 23) { 120 | try { 121 | Class clazz = Settings.class; 122 | Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class); 123 | result = (Boolean) canDrawOverlays.invoke(null, context); 124 | } catch (Exception e) { 125 | Log.e(TAG, Log.getStackTraceString(e)); 126 | } 127 | } 128 | return result; 129 | } 130 | } 131 | 132 | private Dialog applyPermission(Context context) { 133 | if (Build.VERSION.SDK_INT < 23) { 134 | if (RomUtils.checkIsMiuiRom()) { 135 | return miuiROMPermissionApply(context); 136 | } else if (RomUtils.checkIsMeizuRom()) { 137 | return meizuROMPermissionApply(context); 138 | } else if (RomUtils.checkIsHuaweiRom()) { 139 | return huaweiROMPermissionApply(context); 140 | } else if (RomUtils.checkIs360Rom()) { 141 | return ROM360PermissionApply(context); 142 | } else if (RomUtils.checkIsOppoRom()) { 143 | return oppoROMPermissionApply(context); 144 | } 145 | } else { 146 | return commonROMPermissionApply(context); 147 | } 148 | return null; 149 | } 150 | 151 | private Dialog ROM360PermissionApply(final Context context) { 152 | return showConfirmDialog(context, new OnConfirmResult() { 153 | @Override 154 | public void confirmResult(boolean confirm) { 155 | if (confirm) { 156 | QikuUtils.applyPermission(context); 157 | } else { 158 | Log.e(TAG, "ROM:360, user manually refuse OVERLAY_PERMISSION"); 159 | } 160 | } 161 | }); 162 | } 163 | 164 | private Dialog huaweiROMPermissionApply(final Context context) { 165 | return showConfirmDialog(context, new OnConfirmResult() { 166 | @Override 167 | public void confirmResult(boolean confirm) { 168 | if (confirm) { 169 | HuaweiUtils.applyPermission(context); 170 | } else { 171 | Log.e(TAG, "ROM:huawei, user manually refuse OVERLAY_PERMISSION"); 172 | } 173 | } 174 | }); 175 | } 176 | 177 | private Dialog meizuROMPermissionApply(final Context context) { 178 | return showConfirmDialog(context, new OnConfirmResult() { 179 | @Override 180 | public void confirmResult(boolean confirm) { 181 | if (confirm) { 182 | MeizuUtils.applyPermission(context); 183 | } else { 184 | Log.e(TAG, "ROM:meizu, user manually refuse OVERLAY_PERMISSION"); 185 | } 186 | } 187 | }); 188 | } 189 | 190 | private Dialog miuiROMPermissionApply(final Context context) { 191 | return showConfirmDialog(context, new OnConfirmResult() { 192 | @Override 193 | public void confirmResult(boolean confirm) { 194 | if (confirm) { 195 | MiuiUtils.applyMiuiPermission(context); 196 | } else { 197 | Log.e(TAG, "ROM:miui, user manually refuse OVERLAY_PERMISSION"); 198 | } 199 | } 200 | }); 201 | } 202 | 203 | private Dialog oppoROMPermissionApply(final Context context) { 204 | return showConfirmDialog(context, new OnConfirmResult() { 205 | @Override 206 | public void confirmResult(boolean confirm) { 207 | if (confirm) { 208 | OppoUtils.applyOppoPermission(context); 209 | } else { 210 | Log.e(TAG, "ROM:miui, user manually refuse OVERLAY_PERMISSION"); 211 | } 212 | } 213 | }); 214 | } 215 | 216 | /** 217 | * 通用 rom 权限申请 218 | */ 219 | private Dialog commonROMPermissionApply(final Context context) { 220 | //这里也一样,魅族系统需要单独适配 221 | if (RomUtils.checkIsMeizuRom()) { 222 | return meizuROMPermissionApply(context); 223 | } else { 224 | if (Build.VERSION.SDK_INT >= 23) { 225 | return showConfirmDialog(context, new OnConfirmResult() { 226 | @Override 227 | public void confirmResult(boolean confirm) { 228 | if (confirm) { 229 | try { 230 | commonROMPermissionApplyInternal(context); 231 | } catch (Exception e) { 232 | Log.e(TAG, Log.getStackTraceString(e)); 233 | } 234 | } else { 235 | Log.d(TAG, "user manually refuse OVERLAY_PERMISSION"); 236 | //需要做统计效果 237 | } 238 | } 239 | }); 240 | } 241 | } 242 | return null; 243 | } 244 | 245 | public static void commonROMPermissionApplyInternal(Activity activity) { 246 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 247 | if (!Settings.canDrawOverlays(activity)) { 248 | Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 249 | Uri.parse("package:" + activity.getPackageName())); 250 | activity.startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION_CODE); 251 | } 252 | } 253 | } 254 | 255 | public static void commonROMPermissionApplyInternal(Context context) { 256 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 257 | if (!Settings.canDrawOverlays(context)) { 258 | Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 259 | Uri.parse("package:" + context.getPackageName())); 260 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 261 | context.startActivity(intent); 262 | } 263 | } 264 | } 265 | 266 | private Dialog showConfirmDialog(Context context, OnConfirmResult result) { 267 | return showConfirmDialog(context, "您的手机没有授予悬浮窗权限,请开启后再试", result); 268 | } 269 | 270 | /** 271 | * 设置悬浮权限申请提示 272 | * 273 | * @param iPermissionApplyPrompter 274 | * @return 275 | */ 276 | public FloatWindowPermission setIPermissionApplyPrompter(@NonNull IPermissionApplyPrompter iPermissionApplyPrompter) { 277 | mIPermissionApplyPrompter = iPermissionApplyPrompter; 278 | return this; 279 | } 280 | 281 | private Dialog showConfirmDialog(Context context, String message, final OnConfirmResult result) { 282 | return mIPermissionApplyPrompter.showPermissionApplyDialog(context, message, result); 283 | } 284 | 285 | public interface OnConfirmResult { 286 | /** 287 | * @param confirm 是否确认申请悬浮权限 288 | */ 289 | void confirmResult(boolean confirm); 290 | } 291 | 292 | 293 | /** 294 | * 默认悬浮权限申请提示 295 | */ 296 | public class DefaultPermissionApplyPrompter implements IPermissionApplyPrompter { 297 | 298 | @Override 299 | public Dialog showPermissionApplyDialog(Context context, String message, final OnConfirmResult result) { 300 | Dialog dialog = new AlertDialog.Builder(context).setCancelable(true).setTitle("") 301 | .setMessage(message) 302 | .setPositiveButton("现在去开启", 303 | new DialogInterface.OnClickListener() { 304 | @Override 305 | public void onClick(DialogInterface dialog, int which) { 306 | result.confirmResult(true); 307 | dialog.dismiss(); 308 | } 309 | }).setNegativeButton("暂不开启", 310 | new DialogInterface.OnClickListener() { 311 | 312 | @Override 313 | public void onClick(DialogInterface dialog, int which) { 314 | result.confirmResult(false); 315 | dialog.dismiss(); 316 | } 317 | }).create(); 318 | dialog.show(); 319 | return dialog; 320 | } 321 | } 322 | 323 | } 324 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/IPermissionApplyPrompter.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.xfloatview.permission; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | 6 | /** 7 | * 悬浮权限申请提示 8 | * @author xuexiang 9 | * @since 2019-09-16 10:27 10 | */ 11 | public interface IPermissionApplyPrompter { 12 | 13 | /** 14 | * 显示悬浮权限申请提示弹窗 15 | * @param context 16 | * @param message 提示消息 17 | * @param result 确认回调 18 | * @return 19 | */ 20 | Dialog showPermissionApplyDialog(final Context context, final String message, final FloatWindowPermission.OnConfirmResult result); 21 | } 22 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/HuaweiUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission.rom; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.AppOpsManager; 20 | import android.content.ActivityNotFoundException; 21 | import android.content.ComponentName; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.os.Binder; 25 | import android.os.Build; 26 | import android.util.Log; 27 | import android.widget.Toast; 28 | 29 | import java.lang.reflect.Method; 30 | 31 | public class HuaweiUtils { 32 | private static final String TAG = "HuaweiUtils"; 33 | 34 | /** 35 | * 检测 Huawei 悬浮窗权限 36 | */ 37 | public static boolean checkFloatWindowPermission(Context context) { 38 | final int version = Build.VERSION.SDK_INT; 39 | //OP_SYSTEM_ALERT_WINDOW = 24; 40 | return version < 19 || checkOp(context, 24); 41 | } 42 | 43 | /** 44 | * 去华为权限申请页面 45 | */ 46 | public static void applyPermission(Context context) { 47 | try { 48 | Intent intent = new Intent(); 49 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 50 | // ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理 51 | // ComponentName comp = new ComponentName("com.huawei.systemmanager", 52 | // "com.huawei.permissionmanager.ui.SingleAppActivity");//华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决 53 | ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面 54 | intent.setComponent(comp); 55 | if (RomUtils.getEmuiVersion() == 3.1) { 56 | //emui 3.1 的适配 57 | context.startActivity(intent); 58 | } else { 59 | //emui 3.0 的适配 60 | comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//悬浮窗管理页面 61 | intent.setComponent(comp); 62 | context.startActivity(intent); 63 | } 64 | } catch (SecurityException e) { 65 | Intent intent = new Intent(); 66 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 67 | // ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理 68 | ComponentName comp = new ComponentName("com.huawei.systemmanager", 69 | "com.huawei.permissionmanager.ui.MainActivity");//华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决 70 | // ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面 71 | intent.setComponent(comp); 72 | context.startActivity(intent); 73 | Log.e(TAG, Log.getStackTraceString(e)); 74 | } catch (ActivityNotFoundException e) { 75 | /** 76 | * 手机管家版本较低 HUAWEI SC-UL10 77 | */ 78 | // Toast.makeText(MainActivity.this, "act找不到", Toast.LENGTH_LONG).show(); 79 | Intent intent = new Intent(); 80 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 81 | ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");//权限管理页面 android4.4 82 | // ComponentName comp = new ComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决 83 | intent.setComponent(comp); 84 | context.startActivity(intent); 85 | e.printStackTrace(); 86 | Log.e(TAG, Log.getStackTraceString(e)); 87 | } catch (Exception e) { 88 | //抛出异常时提示信息 89 | Toast.makeText(context, "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show(); 90 | Log.e(TAG, Log.getStackTraceString(e)); 91 | } 92 | } 93 | 94 | @TargetApi(Build.VERSION_CODES.KITKAT) 95 | private static boolean checkOp(Context context, int op) { 96 | final int version = Build.VERSION.SDK_INT; 97 | if (version >= 19) { 98 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 99 | try { 100 | Class clazz = AppOpsManager.class; 101 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); 102 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); 103 | } catch (Exception e) { 104 | Log.e(TAG, Log.getStackTraceString(e)); 105 | } 106 | } else { 107 | Log.e(TAG, "Below API 19 cannot invoke!"); 108 | } 109 | return false; 110 | } 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/MeizuUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission.rom; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.AppOpsManager; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.os.Binder; 23 | import android.os.Build; 24 | import android.util.Log; 25 | 26 | import com.xuexiang.xfloatview.permission.FloatWindowPermission; 27 | 28 | import java.lang.reflect.Method; 29 | 30 | public class MeizuUtils { 31 | private static final String TAG = "MeizuUtils"; 32 | 33 | /** 34 | * 检测 meizu 悬浮窗权限 35 | */ 36 | public static boolean checkFloatWindowPermission(Context context) { 37 | final int version = Build.VERSION.SDK_INT; 38 | //OP_SYSTEM_ALERT_WINDOW = 24; 39 | return version < 19 || checkOp(context, 24); 40 | } 41 | 42 | /** 43 | * 去魅族权限申请页面 44 | */ 45 | public static void applyPermission(Context context) { 46 | try { 47 | Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); 48 | // intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity");//remove this line code for fix flyme6.3 49 | intent.putExtra("packageName", context.getPackageName()); 50 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 51 | context.startActivity(intent); 52 | }catch (Exception e) { 53 | try { 54 | Log.e(TAG, "获取悬浮窗权限, 打开AppSecActivity失败, " + Log.getStackTraceString(e)); 55 | // 最新的魅族flyme 6.2.5 用上述方法获取权限失败, 不过又可以用下述方法获取权限了 56 | FloatWindowPermission.commonROMPermissionApplyInternal(context); 57 | } catch (Exception eFinal) { 58 | Log.e(TAG, "获取悬浮窗权限失败, 通用获取方法失败, " + Log.getStackTraceString(eFinal)); 59 | } 60 | } 61 | 62 | } 63 | 64 | @TargetApi(Build.VERSION_CODES.KITKAT) 65 | private static boolean checkOp(Context context, int op) { 66 | final int version = Build.VERSION.SDK_INT; 67 | if (version >= 19) { 68 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 69 | try { 70 | Class clazz = AppOpsManager.class; 71 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); 72 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); 73 | } catch (Exception e) { 74 | Log.e(TAG, Log.getStackTraceString(e)); 75 | } 76 | } else { 77 | Log.e(TAG, "Below API 19 cannot invoke!"); 78 | } 79 | return false; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/MiuiUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission.rom; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.AppOpsManager; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.pm.PackageManager; 23 | import android.net.Uri; 24 | import android.os.Binder; 25 | import android.os.Build; 26 | import android.provider.Settings; 27 | import android.util.Log; 28 | 29 | import java.lang.reflect.Method; 30 | 31 | public class MiuiUtils { 32 | private static final String TAG = "MiuiUtils"; 33 | 34 | /** 35 | * 获取小米 rom 版本号,获取失败返回 -1 36 | * 37 | * @return miui rom version code, if fail , return -1 38 | */ 39 | public static int getMiuiVersion() { 40 | String version = RomUtils.getSystemProperty("ro.miui.ui.version.name"); 41 | if (version != null) { 42 | try { 43 | return Integer.parseInt(version.substring(1)); 44 | } catch (Exception e) { 45 | Log.e(TAG, "get miui version code error, version : " + version); 46 | Log.e(TAG, Log.getStackTraceString(e)); 47 | } 48 | } 49 | return -1; 50 | } 51 | 52 | /** 53 | * 检测 miui 悬浮窗权限 54 | */ 55 | public static boolean checkFloatWindowPermission(Context context) { 56 | final int version = Build.VERSION.SDK_INT; 57 | 58 | if (version >= 19) { 59 | return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; 60 | } else { 61 | // if ((context.getApplicationInfo().flags & 1 << 27) == 1) { 62 | // return true; 63 | // } else { 64 | // return false; 65 | // } 66 | return true; 67 | } 68 | } 69 | 70 | @TargetApi(Build.VERSION_CODES.KITKAT) 71 | private static boolean checkOp(Context context, int op) { 72 | final int version = Build.VERSION.SDK_INT; 73 | if (version >= 19) { 74 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 75 | try { 76 | Class clazz = AppOpsManager.class; 77 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); 78 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); 79 | } catch (Exception e) { 80 | Log.e(TAG, Log.getStackTraceString(e)); 81 | } 82 | } else { 83 | Log.e(TAG, "Below API 19 cannot invoke!"); 84 | } 85 | return false; 86 | } 87 | 88 | /** 89 | * 小米 ROM 权限申请 90 | */ 91 | public static void applyMiuiPermission(Context context) { 92 | int versionCode = getMiuiVersion(); 93 | if (versionCode == 5) { 94 | goToMiuiPermissionActivity_V5(context); 95 | } else if (versionCode == 6) { 96 | goToMiuiPermissionActivity_V6(context); 97 | } else if (versionCode == 7) { 98 | goToMiuiPermissionActivity_V7(context); 99 | } else if (versionCode == 8) { 100 | goToMiuiPermissionActivity_V8(context); 101 | } else { 102 | Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode); 103 | } 104 | } 105 | 106 | private static boolean isIntentAvailable(Intent intent, Context context) { 107 | if (intent == null) { 108 | return false; 109 | } 110 | return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; 111 | } 112 | 113 | /** 114 | * 小米 V5 版本 ROM权限申请 115 | */ 116 | public static void goToMiuiPermissionActivity_V5(Context context) { 117 | Intent intent = null; 118 | String packageName = context.getPackageName(); 119 | intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 120 | Uri uri = Uri.fromParts("package", packageName, null); 121 | intent.setData(uri); 122 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 123 | if (isIntentAvailable(intent, context)) { 124 | context.startActivity(intent); 125 | } else { 126 | Log.e(TAG, "intent is not available!"); 127 | } 128 | 129 | //设置页面在应用详情页面 130 | // Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 131 | // PackageInfo pInfo = null; 132 | // try { 133 | // pInfo = context.getPackageManager().getPackageInfo 134 | // (HostInterfaceManager.getHostInterface().getApp().getPackageName(), 0); 135 | // } catch (PackageManager.NameNotFoundException e) { 136 | // AVLogUtils.e(TAG, e.getMessage()); 137 | // } 138 | // intent.setClassName("com.android.settings", "com.miui.securitycenter.permission.AppPermissionsEditor"); 139 | // intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid); 140 | // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 141 | // if (isIntentAvailable(intent, context)) { 142 | // context.startActivity(intent); 143 | // } else { 144 | // AVLogUtils.e(TAG, "Intent is not available!"); 145 | // } 146 | } 147 | 148 | /** 149 | * 小米 V6 版本 ROM权限申请 150 | */ 151 | public static void goToMiuiPermissionActivity_V6(Context context) { 152 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 153 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); 154 | intent.putExtra("extra_pkgname", context.getPackageName()); 155 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 156 | 157 | if (isIntentAvailable(intent, context)) { 158 | context.startActivity(intent); 159 | } else { 160 | Log.e(TAG, "Intent is not available!"); 161 | } 162 | } 163 | 164 | /** 165 | * 小米 V7 版本 ROM权限申请 166 | */ 167 | public static void goToMiuiPermissionActivity_V7(Context context) { 168 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 169 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); 170 | intent.putExtra("extra_pkgname", context.getPackageName()); 171 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 172 | 173 | if (isIntentAvailable(intent, context)) { 174 | context.startActivity(intent); 175 | } else { 176 | Log.e(TAG, "Intent is not available!"); 177 | } 178 | } 179 | 180 | /** 181 | * 小米 V8 版本 ROM权限申请 182 | */ 183 | public static void goToMiuiPermissionActivity_V8(Context context) { 184 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 185 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); 186 | // intent.setPackage("com.miui.securitycenter"); 187 | intent.putExtra("extra_pkgname", context.getPackageName()); 188 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 189 | 190 | if (isIntentAvailable(intent, context)) { 191 | context.startActivity(intent); 192 | } else { 193 | intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 194 | intent.setPackage("com.miui.securitycenter"); 195 | intent.putExtra("extra_pkgname", context.getPackageName()); 196 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 197 | 198 | if (isIntentAvailable(intent, context)) { 199 | context.startActivity(intent); 200 | } else { 201 | Log.e(TAG, "Intent is not available!"); 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/OppoUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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 | 17 | package com.xuexiang.xfloatview.permission.rom; 18 | 19 | import android.annotation.TargetApi; 20 | import android.app.AppOpsManager; 21 | import android.content.ComponentName; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.os.Binder; 25 | import android.os.Build; 26 | import android.util.Log; 27 | 28 | import java.lang.reflect.Method; 29 | 30 | /** 31 | * Description: 32 | * 33 | * @author Shawn_Dut 34 | * @since 2018-02-01 35 | */ 36 | public class OppoUtils { 37 | 38 | private static final String TAG = "OppoUtils"; 39 | 40 | /** 41 | * 检测 360 悬浮窗权限 42 | */ 43 | public static boolean checkFloatWindowPermission(Context context) { 44 | final int version = Build.VERSION.SDK_INT; 45 | //OP_SYSTEM_ALERT_WINDOW = 24; 46 | return version < 19 || checkOp(context, 24); 47 | } 48 | 49 | @TargetApi(Build.VERSION_CODES.KITKAT) 50 | private static boolean checkOp(Context context, int op) { 51 | final int version = Build.VERSION.SDK_INT; 52 | if (version >= 19) { 53 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 54 | try { 55 | Class clazz = AppOpsManager.class; 56 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); 57 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); 58 | } catch (Exception e) { 59 | Log.e(TAG, Log.getStackTraceString(e)); 60 | } 61 | } else { 62 | Log.e(TAG, "Below API 19 cannot invoke!"); 63 | } 64 | return false; 65 | } 66 | 67 | /** 68 | * oppo ROM 权限申请 69 | */ 70 | public static void applyOppoPermission(Context context) { 71 | //merge request from https://github.com/zhaozepeng/FloatWindowPermission/pull/26 72 | try { 73 | Intent intent = new Intent(); 74 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 75 | //com.coloros.safecenter/.sysfloatwindow.FloatWindowListActivity 76 | ComponentName comp = new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");//悬浮窗管理页面 77 | intent.setComponent(comp); 78 | context.startActivity(intent); 79 | } 80 | catch(Exception e){ 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/QikuUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission.rom; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.AppOpsManager; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.pm.PackageManager; 23 | import android.os.Binder; 24 | import android.os.Build; 25 | import android.util.Log; 26 | 27 | import java.lang.reflect.Method; 28 | 29 | public class QikuUtils { 30 | private static final String TAG = "QikuUtils"; 31 | 32 | /** 33 | * 检测 360 悬浮窗权限 34 | */ 35 | public static boolean checkFloatWindowPermission(Context context) { 36 | final int version = Build.VERSION.SDK_INT; 37 | //OP_SYSTEM_ALERT_WINDOW = 24; 38 | return version < 19 || checkOp(context, 24); 39 | } 40 | 41 | @TargetApi(Build.VERSION_CODES.KITKAT) 42 | private static boolean checkOp(Context context, int op) { 43 | final int version = Build.VERSION.SDK_INT; 44 | if (version >= 19) { 45 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 46 | try { 47 | Class clazz = AppOpsManager.class; 48 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); 49 | return AppOpsManager.MODE_ALLOWED == (int)method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); 50 | } catch (Exception e) { 51 | Log.e(TAG, Log.getStackTraceString(e)); 52 | } 53 | } else { 54 | Log.e("", "Below API 19 cannot invoke!"); 55 | } 56 | return false; 57 | } 58 | 59 | /** 60 | * 去360权限申请页面 61 | */ 62 | public static void applyPermission(Context context) { 63 | Intent intent = new Intent(); 64 | intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity"); 65 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 66 | if (isIntentAvailable(intent, context)) { 67 | context.startActivity(intent); 68 | } else { 69 | intent.setClassName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); 70 | if (isIntentAvailable(intent, context)) { 71 | context.startActivity(intent); 72 | } else { 73 | Log.e(TAG, "can't open permission page with particular name, please use " + 74 | "\"adb shell dumpsys activity\" command and tell me the name of the float window permission page"); 75 | } 76 | } 77 | } 78 | 79 | private static boolean isIntentAvailable(Intent intent, Context context) { 80 | return intent != null && context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /xfloatview-lib/src/main/java/com/xuexiang/xfloatview/permission/rom/RomUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 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.xuexiang.xfloatview.permission.rom; 17 | 18 | import android.os.Build; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | 22 | import java.io.BufferedReader; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | 26 | /** 27 | * Description: 28 | * 29 | * @author zhaozp 30 | * @since 2016-05-23 31 | */ 32 | public class RomUtils { 33 | private static final String TAG = "RomUtils"; 34 | 35 | /** 36 | * 获取 emui 版本号 37 | * 38 | * @return 39 | */ 40 | public static double getEmuiVersion() { 41 | try { 42 | String emuiVersion = getSystemProperty("ro.build.version.emui"); 43 | String version = emuiVersion.substring(emuiVersion.indexOf("_") + 1); 44 | return Double.parseDouble(version); 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return 4.0; 49 | } 50 | 51 | /** 52 | * 获取小米 rom 版本号,获取失败返回 -1 53 | * 54 | * @return miui rom version code, if fail , return -1 55 | */ 56 | public static int getMiuiVersion() { 57 | String version = getSystemProperty("ro.miui.ui.version.name"); 58 | if (version != null) { 59 | try { 60 | return Integer.parseInt(version.substring(1)); 61 | } catch (Exception e) { 62 | Log.e(TAG, "get miui version code error, version : " + version); 63 | } 64 | } 65 | return -1; 66 | } 67 | 68 | public static String getSystemProperty(String propName) { 69 | String line; 70 | BufferedReader input = null; 71 | try { 72 | Process p = Runtime.getRuntime().exec("getprop " + propName); 73 | input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); 74 | line = input.readLine(); 75 | input.close(); 76 | } catch (IOException ex) { 77 | Log.e(TAG, "Unable to read sysprop " + propName, ex); 78 | return null; 79 | } finally { 80 | if (input != null) { 81 | try { 82 | input.close(); 83 | } catch (IOException e) { 84 | Log.e(TAG, "Exception while closing InputStream", e); 85 | } 86 | } 87 | } 88 | return line; 89 | } 90 | 91 | public static boolean checkIsHuaweiRom() { 92 | return Build.MANUFACTURER.contains("HUAWEI"); 93 | } 94 | 95 | /** 96 | * check if is miui ROM 97 | */ 98 | public static boolean checkIsMiuiRom() { 99 | return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")); 100 | } 101 | 102 | public static boolean checkIsMeizuRom() { 103 | //return Build.MANUFACTURER.contains("Meizu"); 104 | String meizuFlymeOSFlag = getSystemProperty("ro.build.display.id"); 105 | return !TextUtils.isEmpty(meizuFlymeOSFlag) && (meizuFlymeOSFlag.contains("flyme") || meizuFlymeOSFlag.toLowerCase().contains("flyme")); 106 | } 107 | 108 | public static boolean checkIs360Rom() { 109 | //fix issue https://github.com/zhaozepeng/FloatWindowPermission/issues/9 110 | return Build.MANUFACTURER.contains("QiKU") 111 | || Build.MANUFACTURER.contains("360"); 112 | } 113 | 114 | public static boolean checkIsOppoRom() { 115 | //https://github.com/zhaozepeng/FloatWindowPermission/pull/26 116 | return Build.MANUFACTURER.contains("OPPO") || Build.MANUFACTURER.contains("oppo"); 117 | } 118 | } 119 | --------------------------------------------------------------------------------