輔助功能 之 小米手機懸浮窗權限
最近做項目遇到小米手機比較人(zhuang)性(bi)化的懸浮窗權限,當在小米手機上安裝完應用後默認是關閉這個權限的,需要用戶手動到應用詳情頁打開該權限。
重(keng)要(die)的是使用這個權限開關係統window後, 小米手機不給任何提示就是不給彈窗。一開始以爲是自己代碼邏輯寫錯了,半天才反應過來,小米還有個這個權限,當天6.0以上安卓系統也需要這個權限,但是會有log提示的。
這麼麻煩的操作怎麼可能讓用戶自己去找應用詳情然後開啓操作呢?本文將實現一鍵開啓小米懸浮窗權限!
1分析問題
想要實現自動調整到改應用的詳情頁的權限管理頁面,就要知道權限管理頁的類名及包名,我們又沒有小米rom的源碼,怎麼才能知道指定頁面的相關信息呢?
查看權限頁面類名
這個方法應該有很多中,但是我只驗證了一種:想到了 adb shell dumpsys activity
usb鏈接電腦後,手動打開應用的詳情頁面裏的權限管理頁面:
類名信息: com.miui.securitycenter/com.miui.permcenter.permissions.AppPermissionsEditorActivity
構造跳轉Intent
知道到了要跳轉的activity,我們直接構造Intent 是否可以直接跳過去?
答案肯定是不行的. Intent 需要構造參數,來區分指定app的權限管理頁面:
/**
* 經測試V5版本是有區別的
* @param context
*/
public void openMiuiPermissionActivity(Context context) {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
if ("V5".equals(getProperty())) {
PackageInfo pInfo = null;
try {
pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e("canking", "error");
}
intent.setClassName("com.miui.securitycenter", "com.miui.securitycenter.permission.AppPermissionsEditor");
intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);
} else {
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
intent.putExtra("extra_pkgname", context.getPackageName());
}
if (isActivityAvailable(context, intent)) {
if (context instanceof Activity) {
Activity a = (Activity) context;
a.startActivityForResult(intent, 2);
}
} else {
Log.e("canking", "Intent is not available!");
}
}
測試適配rom
經測試V5版本和後續版本是後區別的, 分別需要app ID和pkgname. 爲了區分V5版本,我們需要得到小米rom的版本名:
public static String getProperty() {
String property = "null";
if (!"Xiaomi".equals(Build.MANUFACTURER)) {
return property;
}
try {
Class<?> spClazz = Class.forName("android.os.SystemProperties");
Method method = spClazz.getDeclaredMethod("get", String.class, String.class);
property = (String) method.invoke(spClazz, "ro.miui.ui.version.name", null);
} catch (Exception e) {
e.printStackTrace();
}
return property;
}
該反射方法來自網絡,經驗證是有效的.
這樣我們就跳轉到了指定應用的權限管理頁面.
2.實現一鍵打開
標題已經寫了,我們的目標是用戶一鍵開啓,入口做到一鍵就能開啓小米rom懸浮窗權限呢? 可以利用安卓輔助功能自動幫用戶跳轉, 自動點擊打開權限,完成操作後返回.
這裏寫了個BaseAccessibilityService 通用的操作方法封裝在這裏.
/**
* Created by changxing on 16-6-2.
*/
public class BaseAccessService extends AccessibilityService {
@Override
protected void onServiceConnected() {
super.onServiceConnected();
}
@Override
public void onInterrupt() {
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
protected boolean clickByText(AccessibilityNodeInfo nodeInfo, String str) {
if (null != nodeInfo) {
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(str);
if (null != list && list.size() > 0) {
AccessibilityNodeInfo node = list.get(list.size() - 1);
if (node.isClickable()) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
} else {
AccessibilityNodeInfo parentNode = node;
for (int i = 0; i < 5; i++) {
if (null != parentNode) {
parentNode = parentNode.getParent();
if (null != parentNode && parentNode.isClickable()) {
return parentNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
}
}
return false;
}
protected AccessibilityNodeInfo findOpenButton(AccessibilityNodeInfo node) {
if (node == null)
return null;
//非layout元素
if (node.getChildCount() == 0) {
if ("android.widget.Button".equals(node.getClassName())) {
return node;
} else
return null;
}
//layout元素,遍歷找button
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo button = findOpenButton(node.getChild(i));
if (button != null)
return button;
}
return null;
}
}
繼承這個class ,重寫onAccessibilityEvent ,在該方法內處理具體邏輯:
到這裏只監聽TYPE_WINDOW_STATE_CHANGED類型就行了.通過控件的TEXT來實現找到需要點擊的控件.
這裏可以Dump View hierarchy工具來查看我們想要的控件具體信息.
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
int eventType = event.getEventType();
XLogger.v("eventType:" + eventType);
switch (eventType) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
String clazzName = event.getClassName().toString();
AccessibilityNodeInfo nodeInfo = event.getSource();
XLogger.i( "懸浮窗:" + clazzName);
if (clazzName.equals("com.miui.permcenter.permissions.AppPermissionsEditorActivity")) {
if (end) {
clickByText(nodeInfo, "XiaomiPJ");
} else {
boolean access = clickByText(nodeInfo, "顯示懸浮窗");
XLogger.i("access" + access);
}
}
if (clazzName.equals("miui.app.AlertDialog")) {
end = clickByText(nodeInfo, "允許");
XLogger.i( "getClick:" + end);
}
}
}
到這裏就可以實現一鍵開啓小米rom懸浮窗權限了
但是一鍵開啓前我們需要判斷,該權限是否已經開啓:
/**
* 判斷MIUI的懸浮窗權限
* @param context
* @return
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean isMiuiFloatWindowOpAllowed(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
return checkOp(context, 24); // AppOpsManager.OP_SYSTEM_ALERT_WINDOW
} else {
if ((context.getApplicationInfo().flags & 1 << 27) == 1 << 27) {
return true;
} else {
return false;
}
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean checkOp(Context context, int op) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
Class<?> spClazz = Class.forName(manager.getClass().getName());
Method method = manager.getClass().getDeclaredMethod("checkOp", int.class, int.class, String.class);
int property = (Integer) method.invoke(manager, op,
Binder.getCallingUid(), context.getPackageName());
XLogger.e(AppOpsManager.MODE_ALLOWED + " invoke " + property);
if (AppOpsManager.MODE_ALLOWED == property) {
return true;
} else {
return false;
}
} catch (Exception e) {
XLogger.e(e.getMessage());
}
} else {
XLogger.e("Below API 19 cannot invoke!");
}
return false;
}
api>=19需要用反射來活取系統相關配置信息,應該也適用於魅族手機,爲驗證.
這裏我們就實現了一鍵開啓小米Rom懸浮窗權限,並且實現了判斷是否已經開啓了該權限狀態.
個人學習博客
本Demo相關源碼地址: https://github.com/CankingApp/XiaomiPJ, 歡迎下載交流學習~