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