对于那些由于视力、听力或其它身体原因导致不能方便使用Android智能手机的用户,Android提供了Accessibility功能和服务帮助这些用户更加简单地操作设备,包括文字转语音、触觉反馈、手势操作、轨迹球和手柄操作。开发者可以搭建自己的Accessibility服务,这可以加强应用的可用性,例如声音提示,物理反馈,和其他可选的操作模式。
现在我们就来看看利用Accessibility功能如何实现android app的自动安装,原理其实就是弹出系统的app安装界面,然后利用Accessibility的功能来实现模拟点击效果,以达到自动化安装app的功能。
1、实现AccessibilityService服务,加入安装 、卸载操作
public class MyAccessibilityService extends AccessibilityService {
public static String TAG = MyAccessibilityService.class.getSimpleName();
public static String SYS_INSTALL_PACKAGENAME = "com.android.packageinstaller";
public static int NONE = 0;
public static int INSTALLAPK = 1;
public static int UNINSTALLAPK = 2;
public static int myAction = 0;
@Override
public void onAccessibilityEvent(AccessibilityEvent arg0) {
if(arg0.getSource() == null){
Log.e(TAG, "Source = null");
}else{
if(myAction != NONE){
//判断是否是系统的安装界面,不同的机子可能因定制而包名不一样
if(arg0.getPackageName().equals(SYS_INSTALL_PACKAGENAME)){
if(myAction == INSTALLAPK){
installApk(arg0);
}
if(myAction == UNINSTALLAPK){
uninstallApk(arg0);
}
}
}
}
}
@Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
/**
* 根据控件的类型和显示的文字来查找对应的控件,并模拟单击效果
* @param event
* @param name 显示的文字
* @param className 控件的类型
*/
private String checkToClick(AccessibilityEvent event,String name, String className){
String result = "";
List<AccessibilityNodeInfo> arrNodeInfo = event.getSource().findAccessibilityNodeInfosByText(name);
if(arrNodeInfo != null){
for(int i=0;i<arrNodeInfo.size();i++){
AccessibilityNodeInfo nodeInfo = arrNodeInfo.get(i);
if(className.equals(nodeInfo.getClassName()) && nodeInfo.isEnabled()){
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
result = name;
}
}
}
return result;
}
private void installApk(AccessibilityEvent event){
//根据各自的机子进行适配,我这是 安装->完成
checkToClick(event,"安装","android.widget.Button");
String result = checkToClick(event,"完成","android.widget.Button");
if("完成".equals(result)){
myAction = NONE;
}
}
private void uninstallApk(AccessibilityEvent event){
//根据各自的机子进行适配,我这是 确定
String result = checkToClick(event,"确定","android.widget.Button");
if("确定".equals(result)){
myAction = NONE;
}
}
}
2、在Activity调用系统的安装或卸载界面
public class MainActivity extends Activity {
private static String SDPATH = Environment.getExternalStorageDirectory().getAbsolutePath();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(clickListener);
findViewById(R.id.button2).setOnClickListener(clickListener);
}
private OnClickListener clickListener = new OnClickListener(){
@Override
public void onClick(View arg0) {
switch(arg0.getId()){
case R.id.button1:
installApk(SDPATH + "/"+ "test.apk");
break;
case R.id.button2:
uninstallApk("com.xwy.demoapp");
break;
}
}
};
private void installApk(String path) {
MyAccessibilityService.myAction = MyAccessibilityService.INSTALLAPK;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.parse("file://" + path),
"application/vnd.android.package-archive");
startActivity(i);
}
private void uninstallApk(String packageName){
MyAccessibilityService.myAction = MyAccessibilityService.UNINSTALLAPK;
Intent i = new Intent();
i.setAction(Intent.ACTION_DELETE);
i.setData(Uri.parse("package:" + packageName));
startActivity(i);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
3、在AndroidManifest.xml里配置Accessibility服务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xwy.abapkinstalldemo"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="16" />
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:label="@string/service_name" >
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility" />
</service>
</application>
</manifest>
4、设置Accessibility服务的配置选项
<?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=""
android:canRetrieveWindowContent="true"
android:description="@string/description"
android:notificationTimeout="100" />
小结:可能看到这里都认为这个功能是很神奇、很强大,但现实往往并非如此理想。这个辅助功能是可以实现自动点击的功能,但这个服务不能用代码启动,只能进入系统设置界面辅助里找到对应的我们写的Accessibility服务并点按钮打开才能启用。还有的就是这个功能是模拟实现界面点击,但每个机子或许系统的安装界面都不同,适配代码也会写很多,不过还是能实现我们的一些功能,例如批量安装等繁琐操作。