Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
huangzhicong
/
SmartCanteen
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
1aead235
authored
Mar 16, 2020
by
pye52
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
增加守护app服务,当在新设备收到更新指令时,会先检查是否已安装守护app,若未安装则从assets复制文件到SD目录并进行安装。后启动守护app进行主应用更新操作
parent
49a00d12
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
665 additions
and
6 deletions
+665
-6
app/build.gradle
+1
-0
app/src/main/java/com/bgycc/smartcanteen/utils/InstallManager.java
+83
-1
build.gradle
+4
-0
daemon/build.gradle
+14
-5
daemon/proguard-rules.pro
+141
-0
daemon/src/main/java/com/bgycc/smartcanteen/daemon/DaemonService.java
+66
-0
daemon/src/main/java/com/bgycc/smartcanteen/daemon/DangerousUtils.java
+356
-0
No files found.
app/build.gradle
View file @
1aead235
...
...
@@ -13,6 +13,7 @@ android {
ndk
{
abiFilters
"armeabi"
,
"armeabi-v7a"
,
"x86"
,
"mips"
}
buildConfigField
"int"
,
"DaemonVersion"
,
String
.
valueOf
(
rootProject
.
ext
.
daemon_verson_code
)
}
buildTypes
{
debug
{
...
...
app/src/main/java/com/bgycc/smartcanteen/utils/InstallManager.java
View file @
1aead235
package
com
.
bgycc
.
smartcanteen
.
utils
;
import
android.content.ComponentName
;
import
android.content.Intent
;
import
android.os.Build
;
import
com.bgycc.smartcanteen.BuildConfig
;
import
com.bgycc.smartcanteen.MainActivity
;
import
com.blankj.utilcode.util.AppUtils
;
import
com.blankj.utilcode.util.FileUtils
;
import
com.blankj.utilcode.util.LogUtils
;
import
com.blankj.utilcode.util.PathUtils
;
import
com.blankj.utilcode.util.Utils
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
static
com
.
bgycc
.
smartcanteen
.
utils
.
SmartCanteenUtils
.
TAG
;
public
class
InstallManager
{
private
static
final
String
ARG_PATH
=
"arg_path"
;
private
static
final
String
ARG_COMPONENT
=
"arg_package"
;
// assets文件夹中守护进程apk名称
private
static
final
String
DAEMON_APK_NAME
=
"Daemon.apk"
;
// 守护package name
private
static
final
String
DAEMON_PACKAGE_NAME
=
"com.bgycc.smartcanteen.daemon"
;
// 守护服务类名称
private
static
final
String
DAEMON_SERVICE_NAME
=
"com.bgycc.smartcanteen.daemon.DaemonService"
;
public
static
boolean
install
(
File
updateApk
)
{
String
model
=
Build
.
MODEL
;
if
(
Build
.
VERSION
.
SDK_INT
<=
Build
.
VERSION_CODES
.
M
)
{
...
...
@@ -17,14 +36,34 @@ public class InstallManager {
try
{
Process
p
=
Runtime
.
getRuntime
().
exec
(
"chmod 755 "
+
updateApk
);
p
.
waitFor
();
LogUtils
.
d
(
TAG
,
"
开始安装
"
);
LogUtils
.
d
(
TAG
,
"
安装文件权限修改成功
"
);
}
catch
(
Exception
e
)
{
LogUtils
.
e
(
TAG
,
"安装文件权限修改失败"
);
return
false
;
}
}
if
(
model
.
contains
(
DeviceProxy
.
DEVICE_MODEL_TPS
))
{
// 检查守护进程的安装情况,及其安装版本
if
(!
AppUtils
.
isAppInstalled
(
DAEMON_PACKAGE_NAME
))
{
LogUtils
.
d
(
TAG
,
"守护app未安装,从assets复制并进行安装"
);
// 安装守护进程
if
(
installDaemonFromAssets
(
"守护app安装失败,启动静默安装"
,
"assets文件夹中未找到守护apk,或守护app复制失败,启动静默安装"
))
{
return
DangerousUtils
.
installAppSilent
(
updateApk
);
}
}
else
{
// 若已安装,则检查是否需要更新
AppUtils
.
AppInfo
daemonAppInfo
=
AppUtils
.
getAppInfo
(
DAEMON_PACKAGE_NAME
);
if
(
daemonAppInfo
.
getVersionCode
()
<
BuildConfig
.
DaemonVersion
)
{
LogUtils
.
d
(
TAG
,
"当前守护app版本: "
+
daemonAppInfo
.
getVersionCode
()
+
", 最新版本: "
+
BuildConfig
.
DaemonVersion
);
if
(
installDaemonFromAssets
(
"守护app更新失败,启动静默安装"
,
"新版app复制失败,启动静默安装"
))
{
return
DangerousUtils
.
installAppSilent
(
updateApk
);
}
}
}
startDaemonForInstall
(
updateApk
);
return
true
;
}
else
if
(
model
.
contains
(
DeviceProxy
.
DEVICE_MODEL_QUAD
))
{
AppUtils
.
installApp
(
updateApk
);
return
true
;
...
...
@@ -32,4 +71,47 @@ public class InstallManager {
throw
new
RuntimeException
(
"不明设备型号: "
+
model
);
}
}
private
static
void
copyDaemonApkToTarget
(
File
daemonApk
)
throws
IOException
{
InputStream
is
=
Utils
.
getApp
().
getAssets
().
open
(
DAEMON_APK_NAME
);
FileOutputStream
fos
=
new
FileOutputStream
(
daemonApk
);
byte
[]
buffer
=
new
byte
[
1024
];
int
byteCount
;
while
((
byteCount
=
is
.
read
(
buffer
))
!=
-
1
)
{
fos
.
write
(
buffer
,
0
,
byteCount
);
}
fos
.
flush
();
is
.
close
();
fos
.
close
();
}
private
static
boolean
installDaemonFromAssets
(
String
installFailedLog
,
String
copyErrorLog
)
{
File
daemonApk
=
new
File
(
PathUtils
.
getExternalStoragePath
(),
DAEMON_APK_NAME
);
try
{
copyDaemonApkToTarget
(
daemonApk
);
boolean
result
=
DangerousUtils
.
installAppSilent
(
daemonApk
);
LogUtils
.
d
(
TAG
,
"守护app安装结果: "
+
result
);
if
(!
result
)
{
LogUtils
.
d
(
TAG
,
installFailedLog
);
return
true
;
}
}
catch
(
IOException
e
)
{
LogUtils
.
d
(
TAG
,
"复制安装过程出错: "
+
copyErrorLog
,
e
);
return
true
;
}
finally
{
FileUtils
.
delete
(
daemonApk
);
}
return
false
;
}
private
static
void
startDaemonForInstall
(
File
updateApk
)
{
LogUtils
.
d
(
TAG
,
"启动守护app进行更新操作"
);
ComponentName
componentName
=
new
ComponentName
(
DAEMON_PACKAGE_NAME
,
DAEMON_SERVICE_NAME
);
Intent
intent
=
new
Intent
();
intent
.
putExtra
(
ARG_PATH
,
updateApk
.
getAbsoluteFile
());
intent
.
putExtra
(
ARG_COMPONENT
,
BuildConfig
.
APPLICATION_ID
+
File
.
separator
+
MainActivity
.
class
.
getName
());
intent
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
);
intent
.
setComponent
(
componentName
);
Utils
.
getApp
().
startService
(
intent
);
}
}
build.gradle
View file @
1aead235
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript
{
ext
{
daemon_verson_code
=
10
daemon_verson_name
=
"1.0"
}
repositories
{
google
()
jcenter
()
...
...
daemon/build.gradle
View file @
1aead235
...
...
@@ -8,16 +8,25 @@ android {
applicationId
"com.bgycc.smartcanteen.daemon"
minSdkVersion
22
targetSdkVersion
29
versionCode
1
versionName
"1.0"
versionCode
rootProject
.
ext
.
daemon_verson_code
versionName
rootProject
.
ext
.
daemon_verson_name
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
}
buildTypes
{
release
{
debug
{
minifyEnabled
false
proguardFiles
getDefaultProguardFile
(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
proguardFiles
getDefaultProguardFile
(
'proguard-android.txt'
),
'proguard-rules.pro'
}
release
{
postprocessing
{
removeUnusedCode
true
removeUnusedResources
true
obfuscate
false
optimizeCode
true
proguardFiles
'proguard-rules.pro'
}
zipAlignEnabled
true
}
}
compileOptions
{
...
...
daemon/proguard-rules.pro
View file @
1aead235
...
...
@@ -19,3 +19,144 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# -----------------------------基本 -----------------------------
#
# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-
optimizationpasses
5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-
dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-
dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-
dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-
dontoptimize
#
不进行预校验
,
Android
不需要
,
可加快混淆速度。
-
dontpreverify
# 屏蔽警告
-
ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-
optimizations
!
code
/
simplification
/
arithmetic
,!
field
/*
,!class/merging
/*
# 保护代码中的Annotation不被混淆
-keepattributes
*Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
-repackageclasses
# 混淆时记录日志(打印混淆的详细信息)
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
#
# ----------------------------- 默认保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些类不被混淆
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep class android.support.
*
* {*;}## 保留support下的所有类及其内部类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
#----------------------------------------------------
# 保留继承的
-keep public class * extends android.support.v4.
*
*
-keep public class * extends android.support.v7.
*
*
-keep public class * extends android.support.annotation.
*
*
#表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
#当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
-keepclassmembers enum * {
public static
*
*[] values();
public static
*
* valueOf(java.lang.String);
}
#表示不混淆任何一个View中的setXxx()和getXxx()方法,
#因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keep public class * extends android.view.View{
**
* get*();
void set*(
**
*);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#表示不混淆Parcelable实现类中的CREATOR字段,
#毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留R下面的资源
#-keep class
*
*.R$* {
# *;
#}
#不混淆资源类下static的
-keepclassmembers class
*
*.R$* {
public static <fields>;
}
# 对于带有回调函数的onXXEvent、
*
*On*Listener的,不能被混淆
-keepclassmembers class * {
void *(
*
*On*Event);
void *(
*
*On*Listener);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
**
* get*();
void set*(
**
*);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#
# ----------------------------- 第三方 -----------------------------
#
#不混淆smartcanteen下的所有类
-keep class com.bgycc.smartcanteen.
*
* { *; }
daemon/src/main/java/com/bgycc/smartcanteen/daemon/DaemonService.java
View file @
1aead235
package
com
.
bgycc
.
smartcanteen
.
daemon
;
import
android.app.Service
;
import
android.content.ComponentName
;
import
android.content.Intent
;
import
android.os.IBinder
;
import
android.text.TextUtils
;
import
androidx.annotation.Nullable
;
import
com.blankj.utilcode.util.FileUtils
;
import
com.blankj.utilcode.util.LogUtils
;
import
com.blankj.utilcode.util.PathUtils
;
import
com.blankj.utilcode.util.ShellUtils
;
import
java.io.File
;
public
class
DaemonService
extends
Service
{
private
static
final
String
TAG
=
"DaemonService"
;
private
static
final
String
UPDATE_APK
=
"SmartCanteen-update.apk"
;
private
static
final
String
ARG_PATH
=
"arg_path"
;
private
static
final
String
ARG_COMPONENT
=
"arg_package"
;
// 主应用package name
private
static
final
String
MAIN_PACKAGE_NAME
=
"com.bgycc.smartcanteen"
;
// 主应用类名称
private
static
final
String
MAIN_SERVICE_NAME
=
"com.bgycc.smartcanteen.MainActivity"
;
@Override
public
int
onStartCommand
(
Intent
intent
,
int
flags
,
int
startId
)
{
String
path
=
intent
.
getStringExtra
(
ARG_PATH
);
if
(
TextUtils
.
isEmpty
(
path
))
{
path
=
PathUtils
.
getExternalStoragePath
()
+
File
.
separator
+
UPDATE_APK
;
LogUtils
.
d
(
TAG
,
"安装apk地址为空,使用默认地址: "
+
path
);
}
// 当找不到安装apk时,直接启动主应用
if
(!
FileUtils
.
isFileExists
(
path
))
{
LogUtils
.
w
(
TAG
,
"安装失败, 找不到apk"
);
ComponentName
componentName
=
new
ComponentName
(
MAIN_PACKAGE_NAME
,
MAIN_SERVICE_NAME
);
Intent
startMainActivity
=
new
Intent
();
intent
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
);
intent
.
setComponent
(
componentName
);
startActivity
(
startMainActivity
);
stopSelf
(
START_NOT_STICKY
);
return
START_NOT_STICKY
;
}
String
component
=
intent
.
getStringExtra
(
ARG_COMPONENT
);
if
(
TextUtils
.
isEmpty
(
component
))
{
component
=
MAIN_PACKAGE_NAME
+
File
.
separator
+
MAIN_SERVICE_NAME
;
LogUtils
.
d
(
TAG
,
"安装完毕启动包名为空,使用默认包名: "
+
component
);
}
String
command
=
"LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install -r "
+
'"'
+
path
+
'"'
+
";am start -W "
+
component
;
LogUtils
.
d
(
TAG
,
"开始静默安装, path: "
+
path
+
", component: "
+
command
);
ShellUtils
.
CommandResult
commandResult
=
ShellUtils
.
execCmd
(
command
,
isDeviceRooted
());
LogUtils
.
d
(
TAG
,
"安装完毕, success: "
+
commandResult
.
successMsg
+
", error: "
+
commandResult
.
errorMsg
);
FileUtils
.
delete
(
path
);
stopSelf
(
START_NOT_STICKY
);
return
START_NOT_STICKY
;
}
@Nullable
@Override
public
IBinder
onBind
(
Intent
intent
)
{
return
null
;
}
private
static
boolean
isDeviceRooted
()
{
String
su
=
"su"
;
String
[]
locations
=
{
"/system/bin/"
,
"/system/xbin/"
,
"/sbin/"
,
"/system/sd/xbin/"
,
"/system/bin/failsafe/"
,
"/data/local/xbin/"
,
"/data/local/bin/"
,
"/data/local/"
,
"/system/sbin/"
,
"/usr/bin/"
,
"/vendor/bin/"
};
for
(
String
location
:
locations
)
{
if
(
new
File
(
location
+
su
).
exists
())
{
return
true
;
}
}
return
false
;
}
}
daemon/src/main/java/com/bgycc/smartcanteen/daemon/DangerousUtils.java
0 → 100644
View file @
1aead235
package
com
.
bgycc
.
smartcanteen
.
daemon
;
import
android.app.PendingIntent
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.os.Build
;
import
android.os.PowerManager
;
import
android.telephony.SmsManager
;
import
android.telephony.TelephonyManager
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
androidx.annotation.RequiresPermission
;
import
com.blankj.utilcode.util.ShellUtils
;
import
com.blankj.utilcode.util.Utils
;
import
java.io.File
;
import
java.lang.reflect.Method
;
import
java.util.List
;
import
static
android
.
Manifest
.
permission
.
MODIFY_PHONE_STATE
;
import
static
android
.
Manifest
.
permission
.
SEND_SMS
;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2019/06/29
* desc :
* </pre>
*/
public
class
DangerousUtils
{
private
DangerousUtils
()
{
throw
new
UnsupportedOperationException
(
"u can't instantiate me..."
);
}
///////////////////////////////////////////////////////////////////////////
// AppUtils
///////////////////////////////////////////////////////////////////////////
/**
* Install the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.INSTALL_PACKAGES" />}</p>
*
* @param filePath The path of file.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
installAppSilent
(
final
String
filePath
)
{
return
installAppSilent
(
getFileByPath
(
filePath
),
null
);
}
/**
* Install the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.INSTALL_PACKAGES" />}</p>
*
* @param file The file.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
installAppSilent
(
final
File
file
)
{
return
installAppSilent
(
file
,
null
);
}
/**
* Install the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.INSTALL_PACKAGES" />}</p>
*
* @param filePath The path of file.
* @param params The params of installation(e.g.,<code>-r</code>, <code>-s</code>).
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
installAppSilent
(
final
String
filePath
,
final
String
params
)
{
return
installAppSilent
(
getFileByPath
(
filePath
),
params
);
}
/**
* Install the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.INSTALL_PACKAGES" />}</p>
*
* @param file The file.
* @param params The params of installation(e.g.,<code>-r</code>, <code>-s</code>).
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
installAppSilent
(
final
File
file
,
final
String
params
)
{
return
installAppSilent
(
file
,
params
,
isDeviceRooted
());
}
/**
* Install the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.INSTALL_PACKAGES" />}</p>
*
* @param file The file.
* @param params The params of installation(e.g.,<code>-r</code>, <code>-s</code>).
* @param isRooted True to use root, false otherwise.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
installAppSilent
(
final
File
file
,
final
String
params
,
final
boolean
isRooted
)
{
if
(!
isFileExists
(
file
))
return
false
;
String
filePath
=
'"'
+
file
.
getAbsolutePath
()
+
'"'
;
String
command
=
"LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install -r "
+
(
params
==
null
?
""
:
params
+
" "
)
+
filePath
;
ShellUtils
.
CommandResult
commandResult
=
ShellUtils
.
execCmd
(
command
,
isRooted
);
if
(
commandResult
.
successMsg
!=
null
&&
commandResult
.
successMsg
.
toLowerCase
().
contains
(
"success"
))
{
return
true
;
}
else
{
Log
.
e
(
"AppUtils"
,
"installAppSilent successMsg: "
+
commandResult
.
successMsg
+
", errorMsg: "
+
commandResult
.
errorMsg
);
return
false
;
}
}
/**
* Uninstall the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.DELETE_PACKAGES" />}</p>
*
* @param packageName The name of the package.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
uninstallAppSilent
(
final
String
packageName
)
{
return
uninstallAppSilent
(
packageName
,
false
);
}
/**
* Uninstall the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.DELETE_PACKAGES" />}</p>
*
* @param packageName The name of the package.
* @param isKeepData Is keep the data.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
uninstallAppSilent
(
final
String
packageName
,
final
boolean
isKeepData
)
{
return
uninstallAppSilent
(
packageName
,
isKeepData
,
isDeviceRooted
());
}
/**
* Uninstall the app silently.
* <p>Without root permission must hold
* {@code android:sharedUserId="android.uid.shell"} and
* {@code <uses-permission android:name="android.permission.DELETE_PACKAGES" />}</p>
*
* @param packageName The name of the package.
* @param isKeepData Is keep the data.
* @param isRooted True to use root, false otherwise.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
uninstallAppSilent
(
final
String
packageName
,
final
boolean
isKeepData
,
final
boolean
isRooted
)
{
if
(
isSpace
(
packageName
))
return
false
;
String
command
=
"LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm uninstall "
+
(
isKeepData
?
"-k "
:
""
)
+
packageName
;
ShellUtils
.
CommandResult
commandResult
=
ShellUtils
.
execCmd
(
command
,
isRooted
);
if
(
commandResult
.
successMsg
!=
null
&&
commandResult
.
successMsg
.
toLowerCase
().
contains
(
"success"
))
{
return
true
;
}
else
{
Log
.
e
(
"AppUtils"
,
"uninstallAppSilent successMsg: "
+
commandResult
.
successMsg
+
", errorMsg: "
+
commandResult
.
errorMsg
);
return
false
;
}
}
private
static
boolean
isFileExists
(
final
File
file
)
{
return
file
!=
null
&&
file
.
exists
();
}
private
static
File
getFileByPath
(
final
String
filePath
)
{
return
isSpace
(
filePath
)
?
null
:
new
File
(
filePath
);
}
private
static
boolean
isSpace
(
final
String
s
)
{
if
(
s
==
null
)
return
true
;
for
(
int
i
=
0
,
len
=
s
.
length
();
i
<
len
;
++
i
)
{
if
(!
Character
.
isWhitespace
(
s
.
charAt
(
i
)))
{
return
false
;
}
}
return
true
;
}
private
static
boolean
isDeviceRooted
()
{
String
su
=
"su"
;
String
[]
locations
=
{
"/system/bin/"
,
"/system/xbin/"
,
"/sbin/"
,
"/system/sd/xbin/"
,
"/system/bin/failsafe/"
,
"/data/local/xbin/"
,
"/data/local/bin/"
,
"/data/local/"
,
"/system/sbin/"
,
"/usr/bin/"
,
"/vendor/bin/"
};
for
(
String
location
:
locations
)
{
if
(
new
File
(
location
+
su
).
exists
())
{
return
true
;
}
}
return
false
;
}
///////////////////////////////////////////////////////////////////////////
// DeviceUtils
///////////////////////////////////////////////////////////////////////////
/**
* Shutdown the device
* <p>Requires root permission
* or hold {@code android:sharedUserId="android.uid.system"},
* {@code <uses-permission android:name="android.permission.SHUTDOWN" />}
* in manifest.</p>
*
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
shutdown
()
{
try
{
ShellUtils
.
CommandResult
result
=
ShellUtils
.
execCmd
(
"reboot -p"
,
true
);
if
(
result
.
result
==
0
)
return
true
;
Intent
intent
=
new
Intent
(
"android.intent.action.ACTION_REQUEST_SHUTDOWN"
);
intent
.
putExtra
(
"android.intent.extra.KEY_CONFIRM"
,
false
);
Utils
.
getApp
().
startActivity
(
intent
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
));
return
true
;
}
catch
(
Exception
e
)
{
return
false
;
}
}
/**
* Reboot the device.
* <p>Requires root permission
* or hold {@code android:sharedUserId="android.uid.system"} in manifest.</p>
*
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
reboot
()
{
try
{
ShellUtils
.
CommandResult
result
=
ShellUtils
.
execCmd
(
"reboot"
,
true
);
if
(
result
.
result
==
0
)
return
true
;
Intent
intent
=
new
Intent
(
Intent
.
ACTION_REBOOT
);
intent
.
putExtra
(
"nowait"
,
1
);
intent
.
putExtra
(
"interval"
,
1
);
intent
.
putExtra
(
"window"
,
0
);
Utils
.
getApp
().
sendBroadcast
(
intent
);
return
true
;
}
catch
(
Exception
e
)
{
return
false
;
}
}
/**
* Reboot the device.
* <p>Requires root permission
* or hold {@code android:sharedUserId="android.uid.system"},
* {@code <uses-permission android:name="android.permission.REBOOT" />}</p>
*
* @param reason code to pass to the kernel (e.g., "recovery") to
* request special boot modes, or null.
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
reboot
(
final
String
reason
)
{
try
{
PowerManager
pm
=
(
PowerManager
)
Utils
.
getApp
().
getSystemService
(
Context
.
POWER_SERVICE
);
pm
.
reboot
(
reason
);
return
true
;
}
catch
(
Exception
e
)
{
return
false
;
}
}
/**
* Reboot the device to recovery.
* <p>Requires root permission.</p>
*
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
reboot2Recovery
()
{
ShellUtils
.
CommandResult
result
=
ShellUtils
.
execCmd
(
"reboot recovery"
,
true
);
return
result
.
result
==
0
;
}
/**
* Reboot the device to bootloader.
* <p>Requires root permission.</p>
*
* @return {@code true}: success<br>{@code false}: fail
*/
public
static
boolean
reboot2Bootloader
()
{
ShellUtils
.
CommandResult
result
=
ShellUtils
.
execCmd
(
"reboot bootloader"
,
true
);
return
result
.
result
==
0
;
}
/**
* Enable or disable mobile data.
* <p>Must hold {@code android:sharedUserId="android.uid.system"},
* {@code <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />}</p>
*
* @param enabled True to enabled, false otherwise.
* @return {@code true}: success<br>{@code false}: fail
*/
@RequiresPermission
(
MODIFY_PHONE_STATE
)
public
static
boolean
setMobileDataEnabled
(
final
boolean
enabled
)
{
try
{
TelephonyManager
tm
=
(
TelephonyManager
)
Utils
.
getApp
().
getSystemService
(
Context
.
TELEPHONY_SERVICE
);
if
(
tm
==
null
)
return
false
;
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
O
)
{
tm
.
setDataEnabled
(
enabled
);
return
true
;
}
Method
setDataEnabledMethod
=
tm
.
getClass
().
getDeclaredMethod
(
"setDataEnabled"
,
boolean
.
class
);
setDataEnabledMethod
.
invoke
(
tm
,
enabled
);
return
true
;
}
catch
(
Exception
e
)
{
Log
.
e
(
"NetworkUtils"
,
"setMobileDataEnabled: "
,
e
);
}
return
false
;
}
/**
* Send sms silently.
* <p>Must hold {@code <uses-permission android:name="android.permission.SEND_SMS" />}</p>
*
* @param phoneNumber The phone number.
* @param content The content.
*/
@RequiresPermission
(
SEND_SMS
)
public
static
void
sendSmsSilent
(
final
String
phoneNumber
,
final
String
content
)
{
if
(
TextUtils
.
isEmpty
(
content
))
return
;
PendingIntent
sentIntent
=
PendingIntent
.
getBroadcast
(
Utils
.
getApp
(),
0
,
new
Intent
(
"send"
),
0
);
SmsManager
smsManager
=
SmsManager
.
getDefault
();
if
(
content
.
length
()
>=
70
)
{
List
<
String
>
ms
=
smsManager
.
divideMessage
(
content
);
for
(
String
str
:
ms
)
{
smsManager
.
sendTextMessage
(
phoneNumber
,
null
,
str
,
sentIntent
,
null
);
}
}
else
{
smsManager
.
sendTextMessage
(
phoneNumber
,
null
,
content
,
sentIntent
,
null
);
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment