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 */ 16package com.android.systemui.pip.phone; 17 18import android.graphics.Rect; 19import android.graphics.Region; 20import android.os.Bundle; 21import android.os.Handler; 22import android.os.RemoteException; 23import android.view.MagnificationSpec; 24import android.view.accessibility.AccessibilityNodeInfo; 25import android.view.accessibility.AccessibilityWindowInfo; 26import android.view.accessibility.IAccessibilityInteractionConnection; 27import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 28 29import java.util.ArrayList; 30import 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 */ 36public class PipAccessibilityInteractionConnection 37 extends IAccessibilityInteractionConnection.Stub { 38 39 public interface AccessibilityCallbacks { 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 52 public PipAccessibilityInteractionConnection(PipMotionHelper motionHelper, 53 AccessibilityCallbacks callbacks, Handler handler) { 54 mHandler = handler; 55 mMotionHelper = motionHelper; 56 mCallbacks = callbacks; 57 } 58 59 @Override 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 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 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 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 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 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 public static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo() { 169 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 170 info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, 171 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); 172 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 173 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); 174 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW); 175 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 176 info.setImportantForAccessibility(true); 177 info.setClickable(true); 178 info.setVisibleToUser(true); 179 return info; 180 } 181 182 private List<AccessibilityNodeInfo> getNodeList() { 183 if (mAccessibilityNodeInfoList == null) { 184 mAccessibilityNodeInfoList = new ArrayList<>(1); 185 } 186 AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); 187 mAccessibilityNodeInfoList.clear(); 188 mAccessibilityNodeInfoList.add(info); 189 return mAccessibilityNodeInfoList; 190 } 191} 192