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 package com.android.systemui.pip.phone; 17 18 import android.graphics.Rect; 19 import android.graphics.Region; 20 import android.os.Bundle; 21 import android.os.Handler; 22 import android.os.RemoteException; 23 import android.view.MagnificationSpec; 24 import android.view.accessibility.AccessibilityNodeInfo; 25 import android.view.accessibility.AccessibilityWindowInfo; 26 import android.view.accessibility.IAccessibilityInteractionConnection; 27 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Expose the touch actions to accessibility as if this object were a window with a single view. 34 * That pseudo-view exposes all of the actions this object can perform. 35 */ 36 public class PipAccessibilityInteractionConnection 37 extends IAccessibilityInteractionConnection.Stub { 38 39 public interface AccessibilityCallbacks { onAccessibilityShowMenu()40 void onAccessibilityShowMenu(); 41 } 42 43 private static final long ACCESSIBILITY_NODE_ID = 1; 44 private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList; 45 46 private Handler mHandler; 47 private PipMotionHelper mMotionHelper; 48 private AccessibilityCallbacks mCallbacks; 49 50 private Rect mTmpBounds = new Rect(); 51 PipAccessibilityInteractionConnection(PipMotionHelper motionHelper, AccessibilityCallbacks callbacks, Handler handler)52 public PipAccessibilityInteractionConnection(PipMotionHelper motionHelper, 53 AccessibilityCallbacks callbacks, Handler handler) { 54 mHandler = handler; 55 mMotionHelper = motionHelper; 56 mCallbacks = callbacks; 57 } 58 59 @Override findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args)60 public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, 61 Region interactiveRegion, int interactionId, 62 IAccessibilityInteractionConnectionCallback callback, int flags, 63 int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) { 64 try { 65 callback.setFindAccessibilityNodeInfosResult( 66 (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) 67 ? getNodeList() : null, interactionId); 68 } catch (RemoteException re) { 69 /* best effort - ignore */ 70 } 71 } 72 73 @Override performAccessibilityAction(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)74 public void performAccessibilityAction(long accessibilityNodeId, int action, 75 Bundle arguments, int interactionId, 76 IAccessibilityInteractionConnectionCallback callback, int flags, 77 int interrogatingPid, long interrogatingTid) { 78 // We only support one view. A request for anything else is invalid 79 boolean result = false; 80 if (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) { 81 switch (action) { 82 case AccessibilityNodeInfo.ACTION_CLICK: 83 mHandler.post(() -> { 84 mCallbacks.onAccessibilityShowMenu(); 85 }); 86 result = true; 87 break; 88 case AccessibilityNodeInfo.ACTION_DISMISS: 89 mMotionHelper.dismissPip(); 90 result = true; 91 break; 92 case com.android.internal.R.id.accessibilityActionMoveWindow: 93 int newX = arguments.getInt( 94 AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_X); 95 int newY = arguments.getInt( 96 AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_Y); 97 Rect pipBounds = new Rect(); 98 pipBounds.set(mMotionHelper.getBounds()); 99 mTmpBounds.offsetTo(newX, newY); 100 mMotionHelper.movePip(mTmpBounds); 101 result = true; 102 break; 103 case AccessibilityNodeInfo.ACTION_EXPAND: 104 mMotionHelper.expandPip(); 105 result = true; 106 break; 107 default: 108 // Leave result as false 109 } 110 } 111 try { 112 callback.setPerformAccessibilityActionResult(result, interactionId); 113 } catch (RemoteException re) { 114 /* best effort - ignore */ 115 } 116 } 117 118 @Override findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)119 public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, 120 String viewId, Region interactiveRegion, int interactionId, 121 IAccessibilityInteractionConnectionCallback callback, int flags, 122 int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { 123 // We have no view with a proper ID 124 try { 125 callback.setFindAccessibilityNodeInfoResult(null, interactionId); 126 } catch (RemoteException re) { 127 /* best effort - ignore */ 128 } 129 } 130 131 @Override findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)132 public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, 133 Region interactiveRegion, int interactionId, 134 IAccessibilityInteractionConnectionCallback callback, int flags, 135 int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { 136 // We have no view with text 137 try { 138 callback.setFindAccessibilityNodeInfoResult(null, interactionId); 139 } catch (RemoteException re) { 140 /* best effort - ignore */ 141 } 142 } 143 144 @Override findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)145 public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion, 146 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, 147 int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { 148 // We have no view that can take focus 149 try { 150 callback.setFindAccessibilityNodeInfoResult(null, interactionId); 151 } catch (RemoteException re) { 152 /* best effort - ignore */ 153 } 154 } 155 156 @Override focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)157 public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion, 158 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, 159 int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { 160 // We have no view that can take focus 161 try { 162 callback.setFindAccessibilityNodeInfoResult(null, interactionId); 163 } catch (RemoteException re) { 164 /* best effort - ignore */ 165 } 166 } 167 168 @Override clearAccessibilityFocus()169 public void clearAccessibilityFocus() { 170 // We should not be here. 171 } 172 173 @Override notifyOutsideTouch()174 public void notifyOutsideTouch() { 175 // Do nothing. 176 } 177 obtainRootAccessibilityNodeInfo()178 public static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo() { 179 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 180 info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, 181 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); 182 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 183 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); 184 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW); 185 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 186 info.setImportantForAccessibility(true); 187 info.setClickable(true); 188 info.setVisibleToUser(true); 189 return info; 190 } 191 getNodeList()192 private List<AccessibilityNodeInfo> getNodeList() { 193 if (mAccessibilityNodeInfoList == null) { 194 mAccessibilityNodeInfoList = new ArrayList<>(1); 195 } 196 AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); 197 mAccessibilityNodeInfoList.clear(); 198 mAccessibilityNodeInfoList.add(info); 199 return mAccessibilityNodeInfoList; 200 } 201 } 202