基于AccessibilityService实现apk自动安装

时间:2023-03-02 13:19:39


一、概述

        本篇文章主要介绍利用Okhttp实现apk下载并且自动安装,其中用的核心内容就是AccessibilityService,据说很多微信红包也是基于AccessibilityService实现的,后面有时间的话,要好好的了解一下!


二、实现效果图



基于AccessibilityService实现apk自动安装

基于AccessibilityService实现apk自动安装

基于AccessibilityService实现apk自动安装



一旦你在程序中添加了 AccessibilityService,在辅助功能里面就会生成相应的service,默认是关闭,这里我们需要手动开启。

三、实现代码:



MainActivity.java



package com.czhappy.autoinstall.activity;

import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.czhappy.autoinstall.R;
import com.czhappy.autoinstall.utils.OpenOtherApp;
import com.czhappy.autoinstall.utils.SpUtils;
import com.daimajia.numberprogressbar.NumberProgressBar;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.FileCallback;

import java.io.File;

import okhttp3.Call;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

private String TAG = "tag";
/**
* 下载文件的button
*/
private Button mDownLoadFileBtn;
/**
* 获取安装包信息
*/
private PackageInfo mPackageInfo;
/**
* 判断是否安装了天气宝,true表示安装
*/
private boolean isIntalled = false;
private NumberProgressBar number_progress_bar;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViews();
initOnListener();
}

@Override
protected void onResume() {
super.onResume();
initBtnText();

}

/**
* 初始化下载按钮显示的字体,如果安装了天气宝则显示打开,否则显示下载文件
*/
private void initBtnText() {
try {
mPackageInfo = this.getPackageManager().getPackageInfo(
"com.cz.hello", 0);
} catch (PackageManager.NameNotFoundException e) {
mPackageInfo = null;
e.printStackTrace();
}
if (mPackageInfo == null) {
isIntalled = false;
mDownLoadFileBtn.setText(getString(R.string.download_file));

} else {
isIntalled = true;
mDownLoadFileBtn.setText("打开应用");
}
}

/**
* 通过ID得到控件
*
*/
private void findViews() {
mDownLoadFileBtn = (Button) findViewById(R.id.btn_download);
number_progress_bar = (NumberProgressBar) findViewById(R.id.number_progress_bar);
}

/**
* 设置监听事件
*/
private void initOnListener() {
// 引导用户打开辅助功能

// 下载按钮的监听事件
mDownLoadFileBtn.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
if (isIntalled) {
OpenOtherApp.openOtherAPP(MainActivity.this,
"com.cz.hello");// 根据包名打开应用

} else {

downLoadFile();


}

}
});
}

/**
* 下载文件
*/
private void downLoadFile() {
Toast.makeText(this, "文件开始下载", Toast.LENGTH_SHORT).show();
String apkUrl = "http://121.42.53.175:8080/hello_project/resources/upload/TianQiBao201605231.apk";
OkGo.get(apkUrl)//
.tag(this)//
.execute(new FileCallback(Environment.getExternalStorageDirectory() + "/tianqibao/", "tianqibao.apk") { //文件下载时,可以指定下载的文件目录和文件名
@Override
public void onSuccess(File file, Call call, Response response) {
// file 即为文件数据,文件保存在指定目录
Toast.makeText(MainActivity.this, "下载结束", Toast.LENGTH_SHORT).show();
//下载完成将自动安装设置成true,为的是区分我们自己下载的app和其它出处的app
SpUtils.put(MainActivity.this, "IS_AUTO_INSTALL", true);

// 调用安装器去安装我们的apk 一键安装开始啦,如果用户把那个服务打开了的话。
Intent intentInstall = new Intent(android.content.Intent.ACTION_VIEW);
intentInstall.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intentInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
MainActivity.this.startActivity(intentInstall);
}

@Override
public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
//这里回调下载进度(该回调在主线程,可以直接更新ui)
int a = (int) (progress*100);
number_progress_bar.setProgress(a);
}
});
}



}

AutomaticInstallationService.java



package com.czhappy.autoinstall.service;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Environment;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.czhappy.autoinstall.utils.SpUtils;

import java.io.File;
import java.util.List;

/**
* Description:
* User: chenzheng
* Date: 2016/12/8 0008
* Time: 14:35
*/
public class AutomaticInstallationService extends AccessibilityService {
// 大多数的手机包名一样,联想部分机型的手机不一样
private String[] packageNames = { "com.android.packageinstaller", "com.lenovo.security", "com.lenovo.safecenter" };

/**
* 此方法是accessibility service的配置信息 写在java类中是为了向下兼容
*/
@Override
protected void onServiceConnected() {

super.onServiceConnected();

AccessibilityServiceInfo mAccessibilityServiceInfo = new AccessibilityServiceInfo();
// 响应事件的类型,这里是全部的响应事件(长按,单击,滑动等)
mAccessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
// 反馈给用户的类型,这里是语音提示
mAccessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
// 过滤的包名
mAccessibilityServiceInfo.packageNames = packageNames;

// 开启服务后,在偏好设置文件中将"IS_AUTO_INSTALL"的值设为true
SpUtils.put(this, "IS_AUTO_INSTALL", true);

setServiceInfo(mAccessibilityServiceInfo);
}

@Override
public boolean onUnbind(Intent intent) {
// 服务断开后,在偏好设置文件中将"isAllowAutoInstallation"的值设为false
SpUtils.put(this, "IS_AUTO_INSTALL", false);

return super.onUnbind(intent);
}

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
installApplication(event);

}

@Override
public void onInterrupt() {

}

/**
* 查找关键字并执行点击按钮的操作
*
* @param event
*/
@SuppressLint("NewApi")
private void installApplication(AccessibilityEvent event) {
boolean isAutoInstall = (boolean) SpUtils.get(this,"IS_AUTO_INSTALL", false);
// 若是true,是我们自己下载的app,可以执行自动安装,否则为普通安装
if (isAutoInstall == true) {

if (event.getSource() != null && isContainInPackages(event.getPackageName().toString())) {

// 得到“下一步”节点
findNodesByText(event, "下一步");
// 得到“安装”节点
findNodesByText(event, "安装");

// 得到“完成”节点
findNodesByText(event, "完成");
// 得到“打开”节点
findNodesByText(event, "打开");

}
}
}

/**
* 根据文字寻找节点
*
* @param event
* @param text
* 文字
*/

@SuppressLint("NewApi")
private void findNodesByText(AccessibilityEvent event, String text) {
List<AccessibilityNodeInfo> nodes = event.getSource().findAccessibilityNodeInfosByText(text);

if (nodes != null && !nodes.isEmpty()) {
for (AccessibilityNodeInfo info : nodes) {
if (info.isClickable()) {// 只有根据节点信息是下一步,安装,完成,打开,且是可以点击的时候,才执行后面的点击操作
if (text.equals("完成") || text.equals("打开")) {
// 如果安装完成,点击任意一个按钮把允许自动装的值改为false
SpUtils.put(this,"IS_AUTO_INSTALL", false);
File file = new File(Environment.getExternalStorageDirectory() + "/tianqibao/tianqibao.apk");
if (file.exists()) {

file.delete();
}

} else {

info.performAction(AccessibilityNodeInfo.ACTION_CLICK);

}

}

}
}

}

/**
* 判断包名
*
* @param str
* 当前界面包名
* @return
*/
private boolean isContainInPackages(String str) {
boolean flag = false;
for (int i = 0; i < packageNames.length; i++) {
if ((packageNames[i]).equals(str)) {
flag = true;
return flag;
}
}
return flag;
}
}

activity_main.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:orientation="vertical">

<Button
android:id="@+id/btn_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"/>

<com.daimajia.numberprogressbar.NumberProgressBar
android:id="@+id/number_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

AndroidManifest.xml



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.czhappy.autoinstall">

<!-- 使用网络功能所需权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- SD卡读写权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name="com.czhappy.autoinstall.service.AutomaticInstallationService"
android:label="automatic_installation"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>

</application>

</manifest>

accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:packageNames="com.android.packageinstaller"
android:canRetrieveWindowContent="true"
android:description="@string/download_describe"
android:notificationTimeout="100" />

四、如何判断AccessibilityService服务是否开启



private boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = getPackageName() + "/" + AutomaticInstallationService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
String settingValue = Settings.Secure.getString(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();

Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
if (accessibilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}

return false;
}

五、解释说明



AccessibilityService不需要在代码中调用,只要配置了就会在辅助功能中生成,保持监听



onServiceConnected() - 可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。
onAccessibilityEvent() - 必须。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。
onInterrupt() - 必须。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
onUnbind() - 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。