1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.accessibility; 18 19 import android.os.Binder; 20 import android.os.RemoteException; 21 import android.util.Slog; 22 import android.view.accessibility.AccessibilityNodeInfo; 23 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 24 import android.view.accessibility.IAccessibilityInteractionConnection; 25 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 26 27 import com.android.internal.annotations.GuardedBy; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * If we are stripping and/or replacing the actions from a window, we need to intercept the 34 * nodes heading back to the service and swap out the actions. 35 */ 36 public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub { 37 private static final boolean DEBUG = false; 38 private static final String LOG_TAG = "ActionReplacingCallback"; 39 40 private final IAccessibilityInteractionConnectionCallback mServiceCallback; 41 private final IAccessibilityInteractionConnection mConnectionWithReplacementActions; 42 private final int mInteractionId; 43 private final int mNodeWithReplacementActionsInteractionId; 44 private final Object mLock = new Object(); 45 46 @GuardedBy("mLock") 47 private boolean mReplacementNodeIsReadyOrFailed; 48 49 @GuardedBy("mLock") 50 AccessibilityNodeInfo mNodeWithReplacementActions; 51 52 @GuardedBy("mLock") 53 List<AccessibilityNodeInfo> mNodesFromOriginalWindow; 54 55 @GuardedBy("mLock") 56 boolean mSetFindNodeFromOriginalWindowCalled = false; 57 58 @GuardedBy("mLock") 59 AccessibilityNodeInfo mNodeFromOriginalWindow; 60 61 @GuardedBy("mLock") 62 boolean mSetFindNodesFromOriginalWindowCalled = false; 63 64 65 @GuardedBy("mLock") 66 List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow; 67 68 @GuardedBy("mLock") 69 boolean mSetPrefetchFromOriginalWindowCalled = false; 70 71 ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, IAccessibilityInteractionConnection connectionWithReplacementActions, int interactionId, int interrogatingPid, long interrogatingTid)72 public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, 73 IAccessibilityInteractionConnection connectionWithReplacementActions, 74 int interactionId, int interrogatingPid, long interrogatingTid) { 75 mServiceCallback = serviceCallback; 76 mConnectionWithReplacementActions = connectionWithReplacementActions; 77 mInteractionId = interactionId; 78 mNodeWithReplacementActionsInteractionId = interactionId + 1; 79 80 // Request the root node of the replacing window 81 final long identityToken = Binder.clearCallingIdentity(); 82 try { 83 mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId( 84 AccessibilityNodeInfo.ROOT_NODE_ID, null, 85 mNodeWithReplacementActionsInteractionId, this, 0, 86 interrogatingPid, interrogatingTid, null, null, null); 87 } catch (RemoteException re) { 88 if (DEBUG) { 89 Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); 90 } 91 mReplacementNodeIsReadyOrFailed = true; 92 } finally { 93 Binder.restoreCallingIdentity(identityToken); 94 } 95 } 96 97 @Override setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId)98 public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { 99 synchronized (mLock) { 100 if (interactionId == mInteractionId) { 101 mNodeFromOriginalWindow = info; 102 mSetFindNodeFromOriginalWindowCalled = true; 103 } else if (interactionId == mNodeWithReplacementActionsInteractionId) { 104 mNodeWithReplacementActions = info; 105 mReplacementNodeIsReadyOrFailed = true; 106 } else { 107 Slog.e(LOG_TAG, "Callback with unexpected interactionId"); 108 return; 109 } 110 } 111 replaceInfoActionsAndCallServiceIfReady(); 112 } 113 114 @Override setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId)115 public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, 116 int interactionId) { 117 synchronized (mLock) { 118 if (interactionId == mInteractionId) { 119 mNodesFromOriginalWindow = infos; 120 mSetFindNodesFromOriginalWindowCalled = true; 121 } else if (interactionId == mNodeWithReplacementActionsInteractionId) { 122 setNodeWithReplacementActionsFromList(infos); 123 mReplacementNodeIsReadyOrFailed = true; 124 } else { 125 Slog.e(LOG_TAG, "Callback with unexpected interactionId"); 126 return; 127 } 128 } 129 replaceInfoActionsAndCallServiceIfReady(); 130 } 131 132 @Override setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, int interactionId)133 public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, 134 int interactionId) 135 throws RemoteException { 136 synchronized (mLock) { 137 if (interactionId == mInteractionId) { 138 mPrefetchedNodesFromOriginalWindow = infos; 139 mSetPrefetchFromOriginalWindowCalled = true; 140 } else { 141 Slog.e(LOG_TAG, "Callback with unexpected interactionId"); 142 return; 143 } 144 } 145 replaceInfoActionsAndCallServiceIfReady(); 146 } 147 replaceInfoActionsAndCallServiceIfReady()148 private void replaceInfoActionsAndCallServiceIfReady() { 149 replaceInfoActionsAndCallService(); 150 replaceInfosActionsAndCallService(); 151 replacePrefetchInfosActionsAndCallService(); 152 } 153 setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos)154 private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) { 155 for (int i = 0; i < infos.size(); i++) { 156 AccessibilityNodeInfo info = infos.get(i); 157 if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) { 158 mNodeWithReplacementActions = info; 159 } 160 } 161 } 162 163 @Override setPerformAccessibilityActionResult(boolean succeeded, int interactionId)164 public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) 165 throws RemoteException { 166 // There's no reason to use this class when performing actions. Do something reasonable. 167 mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId); 168 } 169 170 @Override sendTakeScreenshotOfWindowError(int errorCode, int interactionId)171 public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId) 172 throws RemoteException { 173 mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId); 174 } 175 replaceInfoActionsAndCallService()176 private void replaceInfoActionsAndCallService() { 177 final AccessibilityNodeInfo nodeToReturn; 178 boolean doCallback = false; 179 synchronized (mLock) { 180 doCallback = mReplacementNodeIsReadyOrFailed 181 && mSetFindNodeFromOriginalWindowCalled; 182 if (doCallback && mNodeFromOriginalWindow != null) { 183 replaceActionsOnInfoLocked(mNodeFromOriginalWindow); 184 mSetFindNodeFromOriginalWindowCalled = false; 185 } 186 nodeToReturn = mNodeFromOriginalWindow; 187 } 188 if (doCallback) { 189 try { 190 mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); 191 } catch (RemoteException re) { 192 if (DEBUG) { 193 Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); 194 } 195 } 196 } 197 } 198 replaceInfosActionsAndCallService()199 private void replaceInfosActionsAndCallService() { 200 List<AccessibilityNodeInfo> nodesToReturn = null; 201 boolean doCallback = false; 202 synchronized (mLock) { 203 doCallback = mReplacementNodeIsReadyOrFailed 204 && mSetFindNodesFromOriginalWindowCalled; 205 if (doCallback) { 206 nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow); 207 mSetFindNodesFromOriginalWindowCalled = false; 208 } 209 } 210 if (doCallback) { 211 try { 212 mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); 213 } catch (RemoteException re) { 214 if (DEBUG) { 215 Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); 216 } 217 } 218 } 219 } 220 replacePrefetchInfosActionsAndCallService()221 private void replacePrefetchInfosActionsAndCallService() { 222 List<AccessibilityNodeInfo> nodesToReturn = null; 223 boolean doCallback = false; 224 synchronized (mLock) { 225 doCallback = mReplacementNodeIsReadyOrFailed 226 && mSetPrefetchFromOriginalWindowCalled; 227 if (doCallback) { 228 nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow); 229 mSetPrefetchFromOriginalWindowCalled = false; 230 } 231 } 232 if (doCallback) { 233 try { 234 mServiceCallback.setPrefetchAccessibilityNodeInfoResult( 235 nodesToReturn, mInteractionId); 236 } catch (RemoteException re) { 237 if (DEBUG) { 238 Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); 239 } 240 } 241 } 242 } 243 244 @GuardedBy("mLock") replaceActionsLocked(List<AccessibilityNodeInfo> infos)245 private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) { 246 if (infos != null) { 247 for (int i = 0; i < infos.size(); i++) { 248 replaceActionsOnInfoLocked(infos.get(i)); 249 } 250 } 251 return (infos == null) 252 ? null : new ArrayList<>(infos); 253 } 254 255 @GuardedBy("mLock") replaceActionsOnInfoLocked(AccessibilityNodeInfo info)256 private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) { 257 info.removeAllActions(); 258 info.setClickable(false); 259 info.setFocusable(false); 260 info.setContextClickable(false); 261 info.setScrollable(false); 262 info.setLongClickable(false); 263 info.setDismissable(false); 264 // We currently only replace actions for the root node 265 if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) 266 && mNodeWithReplacementActions != null) { 267 List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList(); 268 if (actions != null) { 269 for (int j = 0; j < actions.size(); j++) { 270 info.addAction(actions.get(j)); 271 } 272 // The PIP needs to be able to take accessibility focus 273 info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); 274 info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 275 } 276 info.setClickable(mNodeWithReplacementActions.isClickable()); 277 info.setFocusable(mNodeWithReplacementActions.isFocusable()); 278 info.setContextClickable(mNodeWithReplacementActions.isContextClickable()); 279 info.setScrollable(mNodeWithReplacementActions.isScrollable()); 280 info.setLongClickable(mNodeWithReplacementActions.isLongClickable()); 281 info.setDismissable(mNodeWithReplacementActions.isDismissable()); 282 } 283 } 284 } 285