
一、多用戶發(fā)送的廣播是根據(jù)用戶來傳遞的如果你想在切換用戶后對(duì)之前用戶的應(yīng)用做些保存或清理你需要獲取之前用戶的UserHandle并sendBroadcastAsUser這樣發(fā)送的廣播就可以傳遞給上一個(gè)用戶的靜態(tài)廣播接收器了。項(xiàng)目上寫了一個(gè)系統(tǒng)服務(wù)來記錄當(dāng)前的用戶并接收系統(tǒng)切換用戶的廣播切換廣播帶有目標(biāo)用戶的參數(shù)這樣我們就可以發(fā)送不同的廣播給不同的用戶來控制一些全局顯示的功能。import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemProperties; import java.util.HashMap; import android.util.Log; import android.os.UserHandle; import android.os.UserManager; import android.content.BroadcastReceiver; import android.os.Handler; import android.os.HandlerThread; public class MultipleUsersService extends IMultipleUsers.Stub { private static final String TAG MultipleUsersService.class.getSimpleName(); private static MultipleUsersService sInstance; private Context mContext; private UserManager userManager; private UserHandle mCurrentUserHandle; private Handler mHandler; public MultipleUsersService(Context mContext){ this.mContext mContext; sInstance this; userManager (UserManager) mContext.getSystemService(Context.USER_SERVICE); mCurrentUserHandle userManager.getUserForSerialNumber(0); HandlerThread thread new HandlerThread(TAG-1); thread.start(); mHandler new Handler(thread.getLooper()); mContext.registerReceiverAsUser(new DeviceAdminReceiver2(), UserHandle.ALL, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); Log.i(TAG,init ok); } public static MultipleUsersService getInstance(){ return sInstance; } Override public int getCurrentUserSerialNumber() throws RemoteException { return mCurrentUserHandle.getIdentifier(); } public UserHandle getCurrentUser() throws RemoteException { Log.v(TAG,getCurrentUsermCurrentUserHandle); return mCurrentUserHandle; } private class DeviceAdminReceiver2 extends BroadcastReceiver{ Override public void onReceive(Context context,Intent intent) { String action intent.getAction(); //Log.v(TAG, getClass().getName() .onReceive(): received action on user context.getUserId()); if(Intent.ACTION_USER_SWITCHED.equals(action)){ UserHandle switchedUser intent.getParcelableExtra(Intent.EXTRA_USER, android.os.UserHandle.class); Log.i(TAG,onUserSwitched oldmCurrentUserHandle newswitchedUser context.getUserId()); Intent gloAnnIntent new Intent(); gloIntent.setAction(com.lpw.test.action.HIDE_GLOBAL_ANNOTATION); gloIntent.setPackage(com.lpw.test); intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); mContext.sendBroadcastAsUser(gloIntent, mCurrentUserHandle); Log.i(TAG,sendBroadcastAsUser hide glo); Intent showGloAnnIntent new Intent(); showGloIntent.setAction(com.lpw.test.action.SHOW_GLOBAL_ANNOTATION); showGloIntent.setPackage(com.lpw.test); intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); mContext.sendBroadcastAsUser(showGloIntent, mCurrentUserHandle); Log.i(TAG,sendBroadcastAsUser show glo); } } } }二、無障礙服務(wù)使用無障礙服務(wù)監(jiān)聽時(shí)監(jiān)聽到的事件節(jié)點(diǎn)以及它的子節(jié)點(diǎn)可能沒有包含我們想要的組件因此保險(xiǎn)的方法是每次監(jiān)聽到事件都從根節(jié)點(diǎn)遞歸去查找對(duì)應(yīng)的節(jié)點(diǎn)。這里考慮到部分應(yīng)用會(huì)自定義view這樣view的名稱就不是原本的了因此需要比較父類對(duì)象是否是需要尋找的名稱。import android.accessibilityservice.AccessibilityService; import android.app.ActivityManager; import android.content.Context; import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.widget.EditText; import java.util.List; public class MyAccessibilityService extends AccessibilityService { public static ActivityManager mActivityManager; private int lastEventType; private long lastTime; private static final int HANDLER_DELAY 150; Override public void onAccessibilityEvent(AccessibilityEvent event) { int eventType event.getEventType(); LogUtil.d(input_test, eventType: eventType); if (eventType AccessibilityEvent.TYPE_VIEW_FOCUSED || eventType AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { LogUtil.d(input_test, TYPE_VIEW_FOCUSED!); AccessibilityNodeInfo source event.getSource(); AccessibilityNodeInfo root getRootInActiveWindow(); boolean hasFocus refreshNode(source, 1); if(hasFocus) { //識(shí)別到目標(biāo)執(zhí)行操作 } else { AccessibilityNodeInfo focusEdit getFocusEdit(root); if(focusEdit ! null) { Log.d(input_test, MSG_REC); } else { Log.d(input_test, MSG_DEF); } } lastTime System.currentTimeMillis(); } else { if(Math.abs(System.currentTimeMillis() - lastTime) 80) { Log.d(input_test, TYPE_OTHER! return for time!); return; } if(Math.abs(System.currentTimeMillis() - lastTime) 200 eventType lastEventType) { Log.d(input_test, TYPE_OTHER! return for time and event!); return; } if(AccessibilityUtil.mAccessibilityNodeInfo ! null) { boolean refresh AccessibilityUtil.mAccessibilityNodeInfo.refresh(); if(refresh) { CharSequence orgText AccessibilityUtil.mAccessibilityNodeInfo.getText(); CharSequence hintText AccessibilityUtil.mAccessibilityNodeInfo.getHintText(); CharSequence packageName AccessibilityUtil.mAccessibilityNodeInfo.getPackageName(); int textSelectionStart AccessibilityUtil.mAccessibilityNodeInfo.getTextSelectionStart(); int textSelectionEnd AccessibilityUtil.mAccessibilityNodeInfo.getTextSelectionEnd(); boolean focused AccessibilityUtil.mAccessibilityNodeInfo.isFocused(); if(!focused) { //焦點(diǎn)丟失切換到默認(rèn) } } else { //沒有找到目標(biāo)節(jié)點(diǎn)默認(rèn)操作 } } lastEventType eventType; lastTime System.currentTimeMillis(); } } private boolean refreshNode(AccessibilityNodeInfo nodeInfo, int number) { boolean set false; try { if (nodeInfo null || nodeInfo.getPackageName().equals(com.google.android.apps.docs.editors.docs)) { return false; } Class? superClass EditText.class; // 父類類對(duì)象 Class? subClass Class.forName((String) nodeInfo.getClassName()); // 根據(jù)類名獲取子類類對(duì)象 if (superClass.isAssignableFrom(subClass) nodeInfo.isFocused() nodeInfo.getInputType() ! 129) { LogUtil.d(input_test, refreshNode - number - text nodeInfo.getText() - class nodeInfo.getClassName() - inputType nodeInfo.getInputType()); AccessibilityUtil.setAccessibilityNodeInfo(nodeInfo); set true; } } catch (Exception e) { e.printStackTrace(); } return set; } Override protected void onServiceConnected() { super.onServiceConnected(); } private AccessibilityNodeInfo getFocusEdit(AccessibilityNodeInfo source) { if(source null) return null; if(refreshNode(source,3) source.getInputType() ! 129) { return source; } int childCount source.getChildCount(); // 遞歸檢查子節(jié)點(diǎn) for (int i 0; i childCount; i) { AccessibilityNodeInfo child source.getChild(i); if(child null) continue; AccessibilityNodeInfo focusEdit getFocusEdit(child); child.recycle(); if(focusEdit ! null) { return focusEdit; } } source.recycle(); return null; } Override public void onInterrupt() { // 服務(wù)中斷時(shí)的處理 } }三、圖層信任C中使用createSurface創(chuàng)建的圖層如果顯示在GMS部分安全應(yīng)用上并且能夠傳遞觸摸時(shí)谷歌應(yīng)用將不會(huì)處理這部分觸摸事件例如Google Play登錄時(shí)就會(huì)彈出“可能有應(yīng)用阻擋Google Play”。這時(shí)需要給創(chuàng)建的圖層添加信任不然權(quán)限相關(guān)的部分功能都沒法觸摸點(diǎn)擊。SurfaceComposerClient::Transaction{} .setLayer(mSurfaceControl, z_order) .setPosition(mSurfaceControl, left, top) .setSize(mSurfaceControl, mSurfaceWidth, mSurfaceHeight) .setLayerStack(mSurfaceControl, ui::LayerStack::fromValue(layerStack)) .setTrustedOverlay(mSurfaceControl, true) //創(chuàng)建圖層時(shí)設(shè)置可信任 .show(mSurfaceControl) .apply();四、SDK部分Android15應(yīng)用的SDK版本至少需要34更新應(yīng)用的sdk版本后部分功能使用會(huì)報(bào)錯(cuò)需要做修改。//1、注冊(cè)廣播需要顯式的聲明是否EXPORT registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED); //2、pendingIntent需要聲明不可變FLAG_IMMUTABLE或直接指定具體的包名 if (android.os.Build.VERSION.SDK_INT android.os.Build.VERSION_CODES.S) { postAuthorizationIntent.setPackage(com.lpw.filemanager); pendingIntent PendingIntent.getActivity(getContext(), request.hashCode(), postAuthorizationIntent, PendingIntent.FLAG_MUTABLE); } else { pendingIntent PendingIntent.getActivity(getContext(), request.hashCode(), postAuthorizationIntent, 0); }五、OUT鏡像圖層C創(chuàng)建的加速圖層Surface需要配置對(duì)應(yīng)的鏡像圖層才能顯示到OUT連接的副屏上多個(gè)副屏需要配置多個(gè)鏡像圖層否則只有最近接入的副屏才會(huì)顯示加速圖像。//代碼由AI生成具體需要根據(jù)實(shí)際可以接入的副屏數(shù)量來設(shè)置 // 創(chuàng)建主SurfaceControl mSurfaceControl composerClient-createSurface( String8(handwrite_win), mSurfaceWidth, mSurfaceHeight, PIXEL_FORMAT_RGBA_8888, 0); // 創(chuàng)建鏡像SurfaceControl mMirrorControl composerClient-MirrorSurface(mSurfaceControl.get()); mParentControl composerClient-createSurface(String8(MirrorParent), 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer, nullptr); const std::vectorPhysicalDisplayId ids SurfaceComposerClient::getPhysicalDisplayIds(); const size_t size ids.size(); if (size 1) { for (size_t i 0; i size; i) { spIComposerClient token SurfaceComposerClient::getPhysicalDisplayToken(ids[i]); if (i ! 0 token ! nullptr) { ui::DisplayState displayState; auto err SurfaceComposerClient::getDisplayState(token, displayState); if (err ! NO_ERROR) { ALOGE(get display state fail...); } else { // 為第一個(gè)副屏創(chuàng)建并設(shè)置鏡像圖層 if (i 1) { SurfaceComposerClient::Transaction t; t.setLayer(mParentControl, std::numeric_limitsint32_t::max()) .setLayerStack(mParentControl, displayState.layerStack) .show(mParentControl) .apply(); SurfaceComposerClient::Transaction t_mirror; t_mirror.reparent(mMirrorControl, mParentControl) .setLayer(mMirrorControl, std::numeric_limitsint32_t::max()) .setLayerStack(mMirrorControl, displayState.layerStack) .show(mMirrorControl) .apply(); } // 為第二個(gè)副屏創(chuàng)建并設(shè)置鏡像圖層 if (i 2) { // 創(chuàng)建新的鏡像SurfaceControl用于第二個(gè)副屏 spSurfaceControl mMirrorControl2 composerClient-MirrorSurface(mSurfaceControl.get()); spSurfaceControl mParentControl2 composerClient-createSurface(String8(MirrorParent2), 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer, nullptr); SurfaceComposerClient::Transaction t2; t2.setLayer(mParentControl2, std::numeric_limitsint32_t::max()) .setLayerStack(mParentControl2, displayState.layerStack) .show(mParentControl2) .apply(); SurfaceComposerClient::Transaction t_mirror2; t_mirror2.reparent(mMirrorControl2, mParentControl2) .setLayer(mMirrorControl2, std::numeric_limitsint32_t::max()) .setLayerStack(mMirrorControl2, displayState.layerStack) .show(mMirrorControl2) .apply(); } } } } } // 雙屏或更多屏 SurfaceComposerClient::Transaction t; if (LayerStack 0) { // 設(shè)置主圖層 t.setLayer(mSurfaceControl, LayerStack) .apply(); }六、OUT觸控筆部分PEN協(xié)議的副屏上傳的事件不是特別規(guī)范這就導(dǎo)致安卓系統(tǒng)可能報(bào)錯(cuò)重啟這時(shí)需要將錯(cuò)誤等級(jí)降低。// frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. if (CC_UNLIKELY(maskedAction ! AMOTION_EVENT_ACTION_HOVER_MOVE)) { //這里將原本的FATAL等級(jí)改為ERROR避免系統(tǒng)重啟 android::base::LogSeverity severity android::base::LogSeverity::ERROR; if (!input_flags::a11y_crash_on_inconsistent_event_stream() entry.flags AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) { // The Accessibility injected touch exploration event stream // has known inconsistencies, so log ERROR instead of // crashing the device with FATAL. severity android::base::LogSeverity::ERROR; } dump(); LOG(severity) Expected ACTION_HOVER_MOVE instead of entry.getDescription(); } touchedWindow.dispatchMode InputTarget::DispatchMode::AS_IS;異常的觸控筆輸入流程可能導(dǎo)致本機(jī)的窗口拋掉后續(xù)的輸入事件根據(jù)log找到源碼發(fā)現(xiàn)有個(gè)判斷是flag配置的。/** * Return: * true if the incoming event was correctly tracked, * false if the incoming event should be dropped. */ bool InputState::trackMotion(const MotionEntry entry, int32_t flags) { // Dont track non-pointer events if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) { // This is a focus-dispatched event; we dont track its state. return true; } // 添加打印后發(fā)現(xiàn)拋掉輸入的情況大部分走這這個(gè)判斷入口是flag控制的 // 倉庫中查詢到這個(gè)配置是在build目錄下 if (!input_flags::enable_multi_device_same_window_stream()) { if (!mMotionMementos.empty()) { const MotionMemento lastMemento mMotionMementos.back(); if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) !isStylusEvent(entry.source, entry.pointerProperties)) { // We already have a stylus stream, and the new event is not from stylus. LOG(INFO) We already have a stylus stream, and the new event is not from stylus --lpw.; return false; } } } int32_t actionMasked entry.action AMOTION_EVENT_ACTION_MASK; switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_CANCEL: { ssize_t index findMotionMemento(entry, /*hovering*/false); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); return true; } LOG(INFO) AMOTION_EVENT_ACTION_UP findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_DOWN: { ssize_t index findMotionMemento(entry, /*hovering*/false); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); } addMotionMemento(entry, flags, /*hovering*/false); return true; } case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_MOVE: { if (entry.source AINPUT_SOURCE_CLASS_NAVIGATION) { // Trackballs can send MOVE events with a corresponding DOWN or UP. Theres no need // to generate cancellation events for these since theyre based in relative rather // than absolute units. return true; } ssize_t index findMotionMemento(entry, /*hovering*/false); if (entry.source AINPUT_SOURCE_CLASS_JOYSTICK) { // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all // joystick axes are normalized to [-1, 1] we can trust that 0 means its neutral. // Any other value and we need to track the motion so we can send cancellation // events for anything generating fallback events (e.g. DPad keys for joystick // movements). if (index 0) { if (entry.pointerCoords[0].isEmpty()) { mMotionMementos.erase(mMotionMementos.begin() index); } else { MotionMemento memento mMotionMementos[index]; memento.setPointers(entry); } } else if (!entry.pointerCoords[0].isEmpty()) { addMotionMemento(entry, flags, /*hovering*/false); } // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. return true; } if (index 0) { MotionMemento memento mMotionMementos[index]; if (memento.firstNewPointerIdx 0) { memento.setPointers(entry); return true; } } LOG(INFO) AMOTION_EVENT_ACTION_MOVE findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_HOVER_EXIT: { ssize_t index findMotionMemento(entry, /*hovering*/true); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); return true; } LOG(INFO) AMOTION_EVENT_ACTION_HOVER_EXIT findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: { ssize_t index findMotionMemento(entry, /*hovering*/true); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); } addMotionMemento(entry, flags, /*hovering*/true); return true; } default: LOG(INFO) default --lpw.; return true; } }enable_multi_device_same_window_stream_flag_values.textproto 這個(gè)是build目錄下具體的配置文件原本沒有這個(gè)文件這里我添加上文件并設(shè)置enable觸控筆的操作就不會(huì)導(dǎo)致主設(shè)備無法觸摸了。flag_value { package: com.android.input.flags name: enable_multi_device_same_window_stream state: ENABLED permission: READ_WRITE }七、視圖殘留滑動(dòng)ScrollView使得子View一半顯示在邊界處子View執(zhí)行動(dòng)畫后ScrollView頂上有一條藍(lán)線顯示后續(xù)如何滑動(dòng)ScrollView都不會(huì)消失看上去是子View的部分殘留。解決方式子View設(shè)置android:layerTypesoftware分析應(yīng)該是硬件加速導(dǎo)致部分動(dòng)畫顯示殘留。八、開機(jī)自啟動(dòng)設(shè)置開機(jī)后自動(dòng)跳轉(zhuǎn)某個(gè)應(yīng)用但是設(shè)置后開機(jī)跳轉(zhuǎn)會(huì)有一個(gè)滑動(dòng)切換的動(dòng)畫這個(gè)動(dòng)畫默認(rèn)背景是白色與主題不符合并且如果主頁起來后再切換就沒有這個(gè)動(dòng)畫了。解決方式給主頁和FallbackHome都設(shè)置無動(dòng)畫發(fā)現(xiàn)動(dòng)畫還在查看自動(dòng)跳轉(zhuǎn)的邏輯是在主頁onCreate時(shí)判斷跳轉(zhuǎn)的此時(shí)主頁的window可能還沒準(zhǔn)備好就會(huì)導(dǎo)致window動(dòng)畫異常因此在onAttachToWindow中進(jìn)行自動(dòng)跳轉(zhuǎn)就沒有問題了。九、彈窗背景點(diǎn)擊確認(rèn)按鈕后彈窗自動(dòng)漸變消失但是會(huì)有應(yīng)用的名稱在中間一直顯示。分析代碼應(yīng)用原邏輯是啟動(dòng)一個(gè)透明activity統(tǒng)一管理所有彈窗。解決方式activity預(yù)設(shè)了一個(gè)theme主題NoAnimationDialog導(dǎo)致有默認(rèn)背景在AndroidManifest.xml將android:themestyle/NoAnimationDialog一行刪去即可。