1 /*
2  * Copyright (C) 2014 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.wm;
18 
19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
20 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
21 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
22 
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25 
26 import android.animation.ObjectAnimator;
27 import android.animation.ValueAnimator;
28 import android.annotation.NonNull;
29 import android.app.Service;
30 import android.content.Context;
31 import android.graphics.Canvas;
32 import android.graphics.Color;
33 import android.graphics.Matrix;
34 import android.graphics.Paint;
35 import android.graphics.Path;
36 import android.graphics.PixelFormat;
37 import android.graphics.Point;
38 import android.graphics.PorterDuff.Mode;
39 import android.graphics.Rect;
40 import android.graphics.RectF;
41 import android.graphics.Region;
42 import android.os.Handler;
43 import android.os.IBinder;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.text.TextUtils;
47 import android.util.ArraySet;
48 import android.util.Log;
49 import android.util.Slog;
50 import android.util.SparseArray;
51 import android.util.TypedValue;
52 import android.view.Display;
53 import android.view.MagnificationSpec;
54 import android.view.Surface;
55 import android.view.Surface.OutOfResourcesException;
56 import android.view.SurfaceControl;
57 import android.view.ViewConfiguration;
58 import android.view.WindowInfo;
59 import android.view.WindowManager;
60 import android.view.animation.DecelerateInterpolator;
61 import android.view.animation.Interpolator;
62 
63 import com.android.internal.R;
64 import com.android.internal.os.SomeArgs;
65 import com.android.server.policy.WindowManagerPolicy;
66 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
67 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
68 
69 import java.util.ArrayList;
70 import java.util.HashSet;
71 import java.util.List;
72 import java.util.Set;
73 
74 /**
75  * This class contains the accessibility related logic of the window manager.
76  */
77 final class AccessibilityController {
78 
79     private final WindowManagerService mService;
80 
81     private static final float[] sTempFloats = new float[9];
82 
AccessibilityController(WindowManagerService service)83     public AccessibilityController(WindowManagerService service) {
84         mService = service;
85     }
86 
87     private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
88 
89     private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
90 
setMagnificationCallbacksLocked(int displayId, MagnificationCallbacks callbacks)91     public boolean setMagnificationCallbacksLocked(int displayId,
92             MagnificationCallbacks callbacks) {
93         boolean result = false;
94         if (callbacks != null) {
95             if (mDisplayMagnifiers.get(displayId) != null) {
96                 throw new IllegalStateException("Magnification callbacks already set!");
97             }
98             final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
99             if (dc != null) {
100                 final Display display = dc.getDisplay();
101                 if (display != null && display.getType() != Display.TYPE_OVERLAY) {
102                     mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
103                             mService, dc, display, callbacks));
104                     result = true;
105                 }
106             }
107         } else {
108             final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
109             if  (displayMagnifier == null) {
110                 throw new IllegalStateException("Magnification callbacks already cleared!");
111             }
112             displayMagnifier.destroyLocked();
113             mDisplayMagnifiers.remove(displayId);
114             result = true;
115         }
116         return result;
117     }
118 
setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback)119     public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
120         if (callback != null) {
121             if (mWindowsForAccessibilityObserver != null) {
122                 throw new IllegalStateException(
123                         "Windows for accessibility callback already set!");
124             }
125             mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
126                     mService, callback);
127         } else {
128             if (mWindowsForAccessibilityObserver == null) {
129                 throw new IllegalStateException(
130                         "Windows for accessibility callback already cleared!");
131             }
132             mWindowsForAccessibilityObserver = null;
133         }
134     }
135 
performComputeChangedWindowsNotLocked(boolean forceSend)136     public void performComputeChangedWindowsNotLocked(boolean forceSend) {
137         WindowsForAccessibilityObserver observer = null;
138         synchronized (mService) {
139             observer = mWindowsForAccessibilityObserver;
140         }
141         if (observer != null) {
142             observer.performComputeChangedWindowsNotLocked(forceSend);
143         }
144     }
145 
setMagnificationSpecLocked(int displayId, MagnificationSpec spec)146     public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
147         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
148         if (displayMagnifier != null) {
149             displayMagnifier.setMagnificationSpecLocked(spec);
150         }
151         // TODO: support multi-display for windows observer
152         if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
153             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
154         }
155     }
156 
getMagnificationRegionLocked(int displayId, Region outMagnificationRegion)157     public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
158         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
159         if (displayMagnifier != null) {
160             displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
161         }
162     }
163 
onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle)164     public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
165         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
166         if (displayMagnifier != null) {
167             displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
168         }
169         // Not relevant for the window observer.
170     }
171 
onWindowLayersChangedLocked(int displayId)172     public void onWindowLayersChangedLocked(int displayId) {
173         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
174         if (displayMagnifier != null) {
175             displayMagnifier.onWindowLayersChangedLocked();
176         }
177         // TODO: support multi-display for windows observer
178         if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
179             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
180         }
181     }
182 
onRotationChangedLocked(DisplayContent displayContent)183     public void onRotationChangedLocked(DisplayContent displayContent) {
184         final int displayId = displayContent.getDisplayId();
185         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
186         if (displayMagnifier != null) {
187             displayMagnifier.onRotationChangedLocked(displayContent);
188         }
189         // TODO: support multi-display for windows observer
190         if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
191             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
192         }
193     }
194 
onAppWindowTransitionLocked(WindowState windowState, int transition)195     public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
196         final int displayId = windowState.getDisplayId();
197         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
198         if (displayMagnifier != null) {
199             displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
200         }
201         // Not relevant for the window observer.
202     }
203 
onWindowTransitionLocked(WindowState windowState, int transition)204     public void onWindowTransitionLocked(WindowState windowState, int transition) {
205         final int displayId = windowState.getDisplayId();
206         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
207         if (displayMagnifier != null) {
208             displayMagnifier.onWindowTransitionLocked(windowState, transition);
209         }
210         // TODO: support multi-display for windows observer
211         if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
212             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
213         }
214     }
215 
onWindowFocusChangedNotLocked()216     public void onWindowFocusChangedNotLocked() {
217         // Not relevant for the display magnifier.
218 
219         WindowsForAccessibilityObserver observer = null;
220         synchronized (mService) {
221             observer = mWindowsForAccessibilityObserver;
222         }
223         if (observer != null) {
224             observer.performComputeChangedWindowsNotLocked(false);
225         }
226     }
227 
onSomeWindowResizedOrMovedLocked()228     public void onSomeWindowResizedOrMovedLocked() {
229         // Not relevant for the display magnifier.
230 
231         if (mWindowsForAccessibilityObserver != null) {
232             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
233         }
234     }
235 
236     /** NOTE: This has to be called within a surface transaction. */
drawMagnifiedRegionBorderIfNeededLocked(int displayId)237     public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
238         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
239         if (displayMagnifier != null) {
240             displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
241         }
242         // Not relevant for the window observer.
243     }
244 
getMagnificationSpecForWindowLocked(WindowState windowState)245     public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
246         final int displayId = windowState.getDisplayId();
247         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
248         if (displayMagnifier != null) {
249             return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
250         }
251         return null;
252     }
253 
hasCallbacksLocked()254     public boolean hasCallbacksLocked() {
255         // TODO: support multi-display for windows observer
256         return (mDisplayMagnifiers.size() > 0
257                 || mWindowsForAccessibilityObserver != null);
258     }
259 
setForceShowMagnifiableBoundsLocked(int displayId, boolean show)260     public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
261         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
262         if (displayMagnifier != null) {
263             displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
264             displayMagnifier.showMagnificationBoundsIfNeeded();
265         }
266     }
267 
populateTransformationMatrixLocked(WindowState windowState, Matrix outMatrix)268     private static void populateTransformationMatrixLocked(WindowState windowState,
269             Matrix outMatrix) {
270         windowState.getTransformationMatrix(sTempFloats, outMatrix);
271     }
272 
273     /**
274      * This class encapsulates the functionality related to display magnification.
275      */
276     private static final class DisplayMagnifier {
277 
278         private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM;
279 
280         private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
281         private static final boolean DEBUG_ROTATION = false;
282         private static final boolean DEBUG_LAYERS = false;
283         private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
284         private static final boolean DEBUG_VIEWPORT_WINDOW = false;
285 
286         private final Rect mTempRect1 = new Rect();
287         private final Rect mTempRect2 = new Rect();
288 
289         private final Region mTempRegion1 = new Region();
290         private final Region mTempRegion2 = new Region();
291         private final Region mTempRegion3 = new Region();
292         private final Region mTempRegion4 = new Region();
293 
294         private final Context mContext;
295         private final WindowManagerService mService;
296         private final MagnifiedViewport mMagnifedViewport;
297         private final Handler mHandler;
298         private final DisplayContent mDisplayContent;
299         private final Display mDisplay;
300 
301         private final MagnificationCallbacks mCallbacks;
302 
303         private final long mLongAnimationDuration;
304 
305         private boolean mForceShowMagnifiableBounds = false;
306 
DisplayMagnifier(WindowManagerService windowManagerService, DisplayContent displayContent, Display display, MagnificationCallbacks callbacks)307         public DisplayMagnifier(WindowManagerService windowManagerService,
308                 DisplayContent displayContent,
309                 Display display,
310                 MagnificationCallbacks callbacks) {
311             mContext = windowManagerService.mContext;
312             mService = windowManagerService;
313             mCallbacks = callbacks;
314             mDisplayContent = displayContent;
315             mDisplay = display;
316             mHandler = new MyHandler(mService.mH.getLooper());
317             mMagnifedViewport = new MagnifiedViewport();
318             mLongAnimationDuration = mContext.getResources().getInteger(
319                     com.android.internal.R.integer.config_longAnimTime);
320         }
321 
setMagnificationSpecLocked(MagnificationSpec spec)322         public void setMagnificationSpecLocked(MagnificationSpec spec) {
323             mMagnifedViewport.updateMagnificationSpecLocked(spec);
324             mMagnifedViewport.recomputeBoundsLocked();
325 
326             mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
327             mService.scheduleAnimationLocked();
328         }
329 
setForceShowMagnifiableBoundsLocked(boolean show)330         public void setForceShowMagnifiableBoundsLocked(boolean show) {
331             mForceShowMagnifiableBounds = show;
332             mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true);
333         }
334 
isForceShowingMagnifiableBoundsLocked()335         public boolean isForceShowingMagnifiableBoundsLocked() {
336             return mForceShowMagnifiableBounds;
337         }
338 
onRectangleOnScreenRequestedLocked(Rect rectangle)339         public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
340             if (DEBUG_RECTANGLE_REQUESTED) {
341                 Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
342             }
343             if (!mMagnifedViewport.isMagnifyingLocked()) {
344                 return;
345             }
346             Rect magnifiedRegionBounds = mTempRect2;
347             mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
348             if (magnifiedRegionBounds.contains(rectangle)) {
349                 return;
350             }
351             SomeArgs args = SomeArgs.obtain();
352             args.argi1 = rectangle.left;
353             args.argi2 = rectangle.top;
354             args.argi3 = rectangle.right;
355             args.argi4 = rectangle.bottom;
356             mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
357                     args).sendToTarget();
358         }
359 
onWindowLayersChangedLocked()360         public void onWindowLayersChangedLocked() {
361             if (DEBUG_LAYERS) {
362                 Slog.i(LOG_TAG, "Layers changed.");
363             }
364             mMagnifedViewport.recomputeBoundsLocked();
365             mService.scheduleAnimationLocked();
366         }
367 
onRotationChangedLocked(DisplayContent displayContent)368         public void onRotationChangedLocked(DisplayContent displayContent) {
369             if (DEBUG_ROTATION) {
370                 final int rotation = displayContent.getRotation();
371                 Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
372                         + " displayId: " + displayContent.getDisplayId());
373             }
374             mMagnifedViewport.onRotationChangedLocked();
375             mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
376         }
377 
onAppWindowTransitionLocked(WindowState windowState, int transition)378         public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
379             if (DEBUG_WINDOW_TRANSITIONS) {
380                 Slog.i(LOG_TAG, "Window transition: "
381                         + AppTransition.appTransitionToString(transition)
382                         + " displayId: " + windowState.getDisplayId());
383             }
384             final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
385             if (magnifying) {
386                 switch (transition) {
387                     case WindowManager.TRANSIT_ACTIVITY_OPEN:
388                     case WindowManager.TRANSIT_TASK_OPEN:
389                     case WindowManager.TRANSIT_TASK_TO_FRONT:
390                     case WindowManager.TRANSIT_WALLPAPER_OPEN:
391                     case WindowManager.TRANSIT_WALLPAPER_CLOSE:
392                     case WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN: {
393                         mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
394                     }
395                 }
396             }
397         }
398 
onWindowTransitionLocked(WindowState windowState, int transition)399         public void onWindowTransitionLocked(WindowState windowState, int transition) {
400             if (DEBUG_WINDOW_TRANSITIONS) {
401                 Slog.i(LOG_TAG, "Window transition: "
402                         + AppTransition.appTransitionToString(transition)
403                         + " displayId: " + windowState.getDisplayId());
404             }
405             final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
406             final int type = windowState.mAttrs.type;
407             switch (transition) {
408                 case WindowManagerPolicy.TRANSIT_ENTER:
409                 case WindowManagerPolicy.TRANSIT_SHOW: {
410                     if (!magnifying) {
411                         break;
412                     }
413                     switch (type) {
414                         case WindowManager.LayoutParams.TYPE_APPLICATION:
415                         case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION:
416                         case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
417                         case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
418                         case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
419                         case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL:
420                         case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
421                         case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
422                         case WindowManager.LayoutParams.TYPE_PHONE:
423                         case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
424                         case WindowManager.LayoutParams.TYPE_TOAST:
425                         case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
426                         case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
427                         case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
428                         case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
429                         case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
430                         case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
431                         case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
432                         case WindowManager.LayoutParams.TYPE_QS_DIALOG:
433                         case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
434                             Rect magnifiedRegionBounds = mTempRect2;
435                             mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
436                                     magnifiedRegionBounds);
437                             Rect touchableRegionBounds = mTempRect1;
438                             windowState.getTouchableRegion(mTempRegion1);
439                             mTempRegion1.getBounds(touchableRegionBounds);
440                             if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
441                                 mCallbacks.onRectangleOnScreenRequested(
442                                         touchableRegionBounds.left,
443                                         touchableRegionBounds.top,
444                                         touchableRegionBounds.right,
445                                         touchableRegionBounds.bottom);
446                             }
447                         } break;
448                     } break;
449                 }
450             }
451         }
452 
getMagnificationSpecForWindowLocked(WindowState windowState)453         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
454             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
455             if (spec != null && !spec.isNop()) {
456                 if (!windowState.shouldMagnify()) {
457                     return null;
458                 }
459             }
460             return spec;
461         }
462 
getMagnificationRegionLocked(Region outMagnificationRegion)463         public void getMagnificationRegionLocked(Region outMagnificationRegion) {
464             // Make sure we're working with the most current bounds
465             mMagnifedViewport.recomputeBoundsLocked();
466             mMagnifedViewport.getMagnificationRegionLocked(outMagnificationRegion);
467         }
468 
destroyLocked()469         public void destroyLocked() {
470             mMagnifedViewport.destroyWindow();
471         }
472 
473         // Can be called outside of a surface transaction
showMagnificationBoundsIfNeeded()474         public void showMagnificationBoundsIfNeeded() {
475             mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
476                     .sendToTarget();
477         }
478 
479         /** NOTE: This has to be called within a surface transaction. */
drawMagnifiedRegionBorderIfNeededLocked()480         public void drawMagnifiedRegionBorderIfNeededLocked() {
481             mMagnifedViewport.drawWindowIfNeededLocked();
482         }
483 
484         private final class MagnifiedViewport {
485 
486             private final SparseArray<WindowState> mTempWindowStates =
487                     new SparseArray<WindowState>();
488 
489             private final RectF mTempRectF = new RectF();
490 
491             private final Point mTempPoint = new Point();
492 
493             private final Matrix mTempMatrix = new Matrix();
494 
495             private final Region mMagnificationRegion = new Region();
496             private final Region mOldMagnificationRegion = new Region();
497 
498             private final Path mCircularPath;
499 
500             private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
501 
502             private final WindowManager mWindowManager;
503 
504             private final float mBorderWidth;
505             private final int mHalfBorderWidth;
506             private final int mDrawBorderInset;
507 
508             private final ViewportWindow mWindow;
509 
510             private boolean mFullRedrawNeeded;
511             private int mTempLayer = 0;
512 
MagnifiedViewport()513             public MagnifiedViewport() {
514                 mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
515                 mBorderWidth = mContext.getResources().getDimension(
516                         com.android.internal.R.dimen.accessibility_magnification_indicator_width);
517                 mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
518                 mDrawBorderInset = (int) mBorderWidth / 2;
519                 mWindow = new ViewportWindow(mContext);
520 
521                 if (mContext.getResources().getConfiguration().isScreenRound()) {
522                     mCircularPath = new Path();
523                     mDisplay.getRealSize(mTempPoint);
524                     final int centerXY = mTempPoint.x / 2;
525                     mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
526                 } else {
527                     mCircularPath = null;
528                 }
529 
530                 recomputeBoundsLocked();
531             }
532 
getMagnificationRegionLocked(@onNull Region outMagnificationRegion)533             public void getMagnificationRegionLocked(@NonNull Region outMagnificationRegion) {
534                 outMagnificationRegion.set(mMagnificationRegion);
535             }
536 
updateMagnificationSpecLocked(MagnificationSpec spec)537             public void updateMagnificationSpecLocked(MagnificationSpec spec) {
538                 if (spec != null) {
539                     mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
540                 } else {
541                     mMagnificationSpec.clear();
542                 }
543                 // If this message is pending we are in a rotation animation and do not want
544                 // to show the border. We will do so when the pending message is handled.
545                 if (!mHandler.hasMessages(
546                         MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
547                     setMagnifiedRegionBorderShownLocked(
548                             isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true);
549                 }
550             }
551 
recomputeBoundsLocked()552             public void recomputeBoundsLocked() {
553                 mDisplay.getRealSize(mTempPoint);
554                 final int screenWidth = mTempPoint.x;
555                 final int screenHeight = mTempPoint.y;
556 
557                 mMagnificationRegion.set(0, 0, 0, 0);
558                 final Region availableBounds = mTempRegion1;
559                 availableBounds.set(0, 0, screenWidth, screenHeight);
560 
561                 if (mCircularPath != null) {
562                     availableBounds.setPath(mCircularPath, availableBounds);
563                 }
564 
565                 Region nonMagnifiedBounds = mTempRegion4;
566                 nonMagnifiedBounds.set(0, 0, 0, 0);
567 
568                 SparseArray<WindowState> visibleWindows = mTempWindowStates;
569                 visibleWindows.clear();
570                 populateWindowsOnScreenLocked(visibleWindows);
571 
572                 final int visibleWindowCount = visibleWindows.size();
573                 for (int i = visibleWindowCount - 1; i >= 0; i--) {
574                     WindowState windowState = visibleWindows.valueAt(i);
575                     if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
576                             || ((windowState.mAttrs.privateFlags
577                             & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
578                         continue;
579                     }
580 
581                     // Consider the touchable portion of the window
582                     Matrix matrix = mTempMatrix;
583                     populateTransformationMatrixLocked(windowState, matrix);
584                     Region touchableRegion = mTempRegion3;
585                     windowState.getTouchableRegion(touchableRegion);
586                     Rect touchableFrame = mTempRect1;
587                     touchableRegion.getBounds(touchableFrame);
588                     RectF windowFrame = mTempRectF;
589                     windowFrame.set(touchableFrame);
590                     windowFrame.offset(-windowState.getFrameLw().left,
591                             -windowState.getFrameLw().top);
592                     matrix.mapRect(windowFrame);
593                     Region windowBounds = mTempRegion2;
594                     windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
595                             (int) windowFrame.right, (int) windowFrame.bottom);
596                     // Only update new regions
597                     Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
598                     portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
599                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
600                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
601 
602                     if (windowState.shouldMagnify()) {
603                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
604                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
605                     } else {
606                         nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
607                         availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
608                     }
609 
610                     // Count letterbox into nonMagnifiedBounds
611                     if (windowState.isLetterboxedForDisplayCutoutLw()) {
612                         Region letterboxBounds = getLetterboxBounds(windowState);
613                         nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
614                         availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
615                     }
616 
617                     // Update accounted bounds
618                     Region accountedBounds = mTempRegion2;
619                     accountedBounds.set(mMagnificationRegion);
620                     accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
621                     accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
622 
623                     if (accountedBounds.isRect()) {
624                         Rect accountedFrame = mTempRect1;
625                         accountedBounds.getBounds(accountedFrame);
626                         if (accountedFrame.width() == screenWidth
627                                 && accountedFrame.height() == screenHeight) {
628                             break;
629                         }
630                     }
631                 }
632 
633                 visibleWindows.clear();
634 
635                 mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
636                         screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
637                         Region.Op.INTERSECT);
638 
639                 final boolean magnifiedChanged =
640                         !mOldMagnificationRegion.equals(mMagnificationRegion);
641                 if (magnifiedChanged) {
642                     mWindow.setBounds(mMagnificationRegion);
643                     final Rect dirtyRect = mTempRect1;
644                     if (mFullRedrawNeeded) {
645                         mFullRedrawNeeded = false;
646                         dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
647                                 screenWidth - mDrawBorderInset,
648                                 screenHeight - mDrawBorderInset);
649                         mWindow.invalidate(dirtyRect);
650                     } else {
651                         final Region dirtyRegion = mTempRegion3;
652                         dirtyRegion.set(mMagnificationRegion);
653                         dirtyRegion.op(mOldMagnificationRegion, Region.Op.UNION);
654                         dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
655                         dirtyRegion.getBounds(dirtyRect);
656                         mWindow.invalidate(dirtyRect);
657                     }
658 
659                     mOldMagnificationRegion.set(mMagnificationRegion);
660                     final SomeArgs args = SomeArgs.obtain();
661                     args.arg1 = Region.obtain(mMagnificationRegion);
662                     mHandler.obtainMessage(
663                             MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
664                             .sendToTarget();
665                 }
666             }
667 
getLetterboxBounds(WindowState windowState)668             private Region getLetterboxBounds(WindowState windowState) {
669                 final AppWindowToken appToken = windowState.mAppToken;
670                 if (appToken == null) {
671                     return new Region();
672                 }
673 
674                 mDisplay.getRealSize(mTempPoint);
675                 final Rect letterboxInsets = appToken.getLetterboxInsets();
676                 final int screenWidth = mTempPoint.x;
677                 final int screenHeight = mTempPoint.y;
678                 final Rect nonLetterboxRect = mTempRect1;
679                 final Region letterboxBounds = mTempRegion3;
680                 nonLetterboxRect.set(0, 0, screenWidth, screenHeight);
681                 nonLetterboxRect.inset(letterboxInsets);
682                 letterboxBounds.set(0, 0, screenWidth, screenHeight);
683                 letterboxBounds.op(nonLetterboxRect, Region.Op.DIFFERENCE);
684                 return letterboxBounds;
685             }
686 
onRotationChangedLocked()687             public void onRotationChangedLocked() {
688                 // If we are showing the magnification border, hide it immediately so
689                 // the user does not see strange artifacts during rotation. The screenshot
690                 // used for rotation already has the border. After the rotation is complete
691                 // we will show the border.
692                 if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
693                     setMagnifiedRegionBorderShownLocked(false, false);
694                     final long delay = (long) (mLongAnimationDuration
695                             * mService.getWindowAnimationScaleLocked());
696                     Message message = mHandler.obtainMessage(
697                             MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
698                     mHandler.sendMessageDelayed(message, delay);
699                 }
700                 recomputeBoundsLocked();
701                 mWindow.updateSize();
702             }
703 
setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate)704             public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
705                 if (shown) {
706                     mFullRedrawNeeded = true;
707                     mOldMagnificationRegion.set(0, 0, 0, 0);
708                 }
709                 mWindow.setShown(shown, animate);
710             }
711 
getMagnifiedFrameInContentCoordsLocked(Rect rect)712             public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
713                 MagnificationSpec spec = mMagnificationSpec;
714                 mMagnificationRegion.getBounds(rect);
715                 rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
716                 rect.scale(1.0f / spec.scale);
717             }
718 
isMagnifyingLocked()719             public boolean isMagnifyingLocked() {
720                 return mMagnificationSpec.scale > 1.0f;
721             }
722 
getMagnificationSpecLocked()723             public MagnificationSpec getMagnificationSpecLocked() {
724                 return mMagnificationSpec;
725             }
726 
727             /** NOTE: This has to be called within a surface transaction. */
drawWindowIfNeededLocked()728             public void drawWindowIfNeededLocked() {
729                 recomputeBoundsLocked();
730                 mWindow.drawIfNeeded();
731             }
732 
destroyWindow()733             public void destroyWindow() {
734                 mWindow.releaseSurface();
735             }
736 
populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows)737             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
738                 mTempLayer = 0;
739                 mDisplayContent.forAllWindows((w) -> {
740                     if (w.isOnScreen() && w.isVisibleLw()
741                             && (w.mAttrs.alpha != 0)
742                             && !w.mWinAnimator.mEnterAnimationPending) {
743                         mTempLayer++;
744                         outWindows.put(mTempLayer, w);
745                     }
746                 }, false /* traverseTopToBottom */ );
747             }
748 
749             private final class ViewportWindow {
750                 private static final String SURFACE_TITLE = "Magnification Overlay";
751 
752                 private final Region mBounds = new Region();
753                 private final Rect mDirtyRect = new Rect();
754                 private final Paint mPaint = new Paint();
755 
756                 private final SurfaceControl mSurfaceControl;
757                 private final Surface mSurface = new Surface();
758 
759                 private final AnimationController mAnimationController;
760 
761                 private boolean mShown;
762                 private int mAlpha;
763 
764                 private boolean mInvalidated;
765 
ViewportWindow(Context context)766                 public ViewportWindow(Context context) {
767                     SurfaceControl surfaceControl = null;
768                     try {
769                         mDisplay.getRealSize(mTempPoint);
770                         surfaceControl = mDisplayContent
771                                 .makeOverlay()
772                                 .setName(SURFACE_TITLE)
773                                 .setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
774                                 .setFormat(PixelFormat.TRANSLUCENT)
775                                 .build();
776                     } catch (OutOfResourcesException oore) {
777                         /* ignore */
778                     }
779                     mSurfaceControl = surfaceControl;
780                     mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
781                             TYPE_MAGNIFICATION_OVERLAY)
782                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
783                     mSurfaceControl.setPosition(0, 0);
784                     mSurface.copyFrom(mSurfaceControl);
785 
786                     mAnimationController = new AnimationController(context,
787                             mService.mH.getLooper());
788 
789                     TypedValue typedValue = new TypedValue();
790                     context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
791                             typedValue, true);
792                     final int borderColor = context.getColor(typedValue.resourceId);
793 
794                     mPaint.setStyle(Paint.Style.STROKE);
795                     mPaint.setStrokeWidth(mBorderWidth);
796                     mPaint.setColor(borderColor);
797 
798                     mInvalidated = true;
799                 }
800 
setShown(boolean shown, boolean animate)801                 public void setShown(boolean shown, boolean animate) {
802                     synchronized (mService.mGlobalLock) {
803                         if (mShown == shown) {
804                             return;
805                         }
806                         mShown = shown;
807                         mAnimationController.onFrameShownStateChanged(shown, animate);
808                         if (DEBUG_VIEWPORT_WINDOW) {
809                             Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
810                         }
811                     }
812                 }
813 
814                 @SuppressWarnings("unused")
815                 // Called reflectively from an animator.
getAlpha()816                 public int getAlpha() {
817                     synchronized (mService.mGlobalLock) {
818                         return mAlpha;
819                     }
820                 }
821 
setAlpha(int alpha)822                 public void setAlpha(int alpha) {
823                     synchronized (mService.mGlobalLock) {
824                         if (mAlpha == alpha) {
825                             return;
826                         }
827                         mAlpha = alpha;
828                         invalidate(null);
829                         if (DEBUG_VIEWPORT_WINDOW) {
830                             Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
831                         }
832                     }
833                 }
834 
setBounds(Region bounds)835                 public void setBounds(Region bounds) {
836                     synchronized (mService.mGlobalLock) {
837                         if (mBounds.equals(bounds)) {
838                             return;
839                         }
840                         mBounds.set(bounds);
841                         invalidate(mDirtyRect);
842                         if (DEBUG_VIEWPORT_WINDOW) {
843                             Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
844                         }
845                     }
846                 }
847 
updateSize()848                 public void updateSize() {
849                     synchronized (mService.mGlobalLock) {
850                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
851                         mSurfaceControl.setBufferSize(mTempPoint.x, mTempPoint.y);
852                         invalidate(mDirtyRect);
853                     }
854                 }
855 
invalidate(Rect dirtyRect)856                 public void invalidate(Rect dirtyRect) {
857                     if (dirtyRect != null) {
858                         mDirtyRect.set(dirtyRect);
859                     } else {
860                         mDirtyRect.setEmpty();
861                     }
862                     mInvalidated = true;
863                     mService.scheduleAnimationLocked();
864                 }
865 
866                 /** NOTE: This has to be called within a surface transaction. */
drawIfNeeded()867                 public void drawIfNeeded() {
868                     synchronized (mService.mGlobalLock) {
869                         if (!mInvalidated) {
870                             return;
871                         }
872                         mInvalidated = false;
873                         if (mAlpha > 0) {
874                             Canvas canvas = null;
875                             try {
876                                 // Empty dirty rectangle means unspecified.
877                                 if (mDirtyRect.isEmpty()) {
878                                     mBounds.getBounds(mDirtyRect);
879                                 }
880                                 mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
881                                 canvas = mSurface.lockCanvas(mDirtyRect);
882                                 if (DEBUG_VIEWPORT_WINDOW) {
883                                     Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
884                                 }
885                             } catch (IllegalArgumentException iae) {
886                                 /* ignore */
887                             } catch (Surface.OutOfResourcesException oore) {
888                                 /* ignore */
889                             }
890                             if (canvas == null) {
891                                 return;
892                             }
893                             if (DEBUG_VIEWPORT_WINDOW) {
894                                 Slog.i(LOG_TAG, "Bounds: " + mBounds);
895                             }
896                             canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
897                             mPaint.setAlpha(mAlpha);
898                             Path path = mBounds.getBoundaryPath();
899                             canvas.drawPath(path, mPaint);
900 
901                             mSurface.unlockCanvasAndPost(canvas);
902                             mSurfaceControl.show();
903                         } else {
904                             mSurfaceControl.hide();
905                         }
906                     }
907                 }
908 
releaseSurface()909                 public void releaseSurface() {
910                     mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
911                     mSurface.release();
912                 }
913 
914                 private final class AnimationController extends Handler {
915                     private static final String PROPERTY_NAME_ALPHA = "alpha";
916 
917                     private static final int MIN_ALPHA = 0;
918                     private static final int MAX_ALPHA = 255;
919 
920                     private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
921 
922                     private final ValueAnimator mShowHideFrameAnimator;
923 
AnimationController(Context context, Looper looper)924                     public AnimationController(Context context, Looper looper) {
925                         super(looper);
926                         mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
927                                 PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
928 
929                         Interpolator interpolator = new DecelerateInterpolator(2.5f);
930                         final long longAnimationDuration = context.getResources().getInteger(
931                                 com.android.internal.R.integer.config_longAnimTime);
932 
933                         mShowHideFrameAnimator.setInterpolator(interpolator);
934                         mShowHideFrameAnimator.setDuration(longAnimationDuration);
935                     }
936 
onFrameShownStateChanged(boolean shown, boolean animate)937                     public void onFrameShownStateChanged(boolean shown, boolean animate) {
938                         obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
939                                 shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
940                     }
941 
942                     @Override
handleMessage(Message message)943                     public void handleMessage(Message message) {
944                         switch (message.what) {
945                             case MSG_FRAME_SHOWN_STATE_CHANGED: {
946                                 final boolean shown = message.arg1 == 1;
947                                 final boolean animate = message.arg2 == 1;
948 
949                                 if (animate) {
950                                     if (mShowHideFrameAnimator.isRunning()) {
951                                         mShowHideFrameAnimator.reverse();
952                                     } else {
953                                         if (shown) {
954                                             mShowHideFrameAnimator.start();
955                                         } else {
956                                             mShowHideFrameAnimator.reverse();
957                                         }
958                                     }
959                                 } else {
960                                     mShowHideFrameAnimator.cancel();
961                                     if (shown) {
962                                         setAlpha(MAX_ALPHA);
963                                     } else {
964                                         setAlpha(MIN_ALPHA);
965                                     }
966                                 }
967                             } break;
968                         }
969                     }
970                 }
971             }
972         }
973 
974         private class MyHandler extends Handler {
975             public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
976             public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
977             public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
978             public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
979             public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
980 
MyHandler(Looper looper)981             public MyHandler(Looper looper) {
982                 super(looper);
983             }
984 
985             @Override
handleMessage(Message message)986             public void handleMessage(Message message) {
987                 switch (message.what) {
988                     case MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED: {
989                         final SomeArgs args = (SomeArgs) message.obj;
990                         final Region magnifiedBounds = (Region) args.arg1;
991                         mCallbacks.onMagnificationRegionChanged(magnifiedBounds);
992                         magnifiedBounds.recycle();
993                     } break;
994 
995                     case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
996                         SomeArgs args = (SomeArgs) message.obj;
997                         final int left = args.argi1;
998                         final int top = args.argi2;
999                         final int right = args.argi3;
1000                         final int bottom = args.argi4;
1001                         mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
1002                         args.recycle();
1003                     } break;
1004 
1005                     case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
1006                         mCallbacks.onUserContextChanged();
1007                     } break;
1008 
1009                     case MESSAGE_NOTIFY_ROTATION_CHANGED: {
1010                         final int rotation = message.arg1;
1011                         mCallbacks.onRotationChanged(rotation);
1012                     } break;
1013 
1014                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
1015                         synchronized (mService.mGlobalLock) {
1016                             if (mMagnifedViewport.isMagnifyingLocked()
1017                                     || isForceShowingMagnifiableBoundsLocked()) {
1018                                 mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
1019                                 mService.scheduleAnimationLocked();
1020                             }
1021                         }
1022                     } break;
1023                 }
1024             }
1025         }
1026     }
1027 
1028     /**
1029      * This class encapsulates the functionality related to computing the windows
1030      * reported for accessibility purposes. These windows are all windows a sighted
1031      * user can see on the screen.
1032      */
1033     private static final class WindowsForAccessibilityObserver {
1034         private static final String LOG_TAG = TAG_WITH_CLASS_NAME ?
1035                 "WindowsForAccessibilityObserver" : TAG_WM;
1036 
1037         private static final boolean DEBUG = false;
1038 
1039         private final SparseArray<WindowState> mTempWindowStates =
1040                 new SparseArray<WindowState>();
1041 
1042         private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>();
1043 
1044         private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>();
1045 
1046         private final RectF mTempRectF = new RectF();
1047 
1048         private final Matrix mTempMatrix = new Matrix();
1049 
1050         private final Point mTempPoint = new Point();
1051 
1052         private final Rect mTempRect = new Rect();
1053 
1054         private final Region mTempRegion = new Region();
1055 
1056         private final Region mTempRegion1 = new Region();
1057 
1058         private final Context mContext;
1059 
1060         private final WindowManagerService mService;
1061 
1062         private final Handler mHandler;
1063 
1064         private final WindowsForAccessibilityCallback mCallback;
1065 
1066         private final long mRecurringAccessibilityEventsIntervalMillis;
1067 
WindowsForAccessibilityObserver(WindowManagerService windowManagerService, WindowsForAccessibilityCallback callback)1068         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
1069                 WindowsForAccessibilityCallback callback) {
1070             mContext = windowManagerService.mContext;
1071             mService = windowManagerService;
1072             mCallback = callback;
1073             mHandler = new MyHandler(mService.mH.getLooper());
1074             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
1075                     .getSendRecurringAccessibilityEventsInterval();
1076             computeChangedWindows(true);
1077         }
1078 
performComputeChangedWindowsNotLocked(boolean forceSend)1079         public void performComputeChangedWindowsNotLocked(boolean forceSend) {
1080             mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
1081             computeChangedWindows(forceSend);
1082         }
1083 
scheduleComputeChangedWindowsLocked()1084         public void scheduleComputeChangedWindowsLocked() {
1085             if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
1086                 mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
1087                         mRecurringAccessibilityEventsIntervalMillis);
1088             }
1089         }
1090 
1091         /**
1092          * Check if windows have changed, and send them to the accessibility subsystem if they have.
1093          *
1094          * @param forceSend Send the windows the accessibility even if they haven't changed.
1095          */
computeChangedWindows(boolean forceSend)1096         public void computeChangedWindows(boolean forceSend) {
1097             if (DEBUG) {
1098                 Slog.i(LOG_TAG, "computeChangedWindows()");
1099             }
1100 
1101             boolean windowsChanged = false;
1102             List<WindowInfo> windows = new ArrayList<WindowInfo>();
1103 
1104             synchronized (mService.mGlobalLock) {
1105                 // Do not send the windows if there is no current focus as
1106                 // the window manager is still looking for where to put it.
1107                 // We will do the work when we get a focus change callback.
1108                 // TODO(b/112273690): Support multiple displays
1109                 if (!isCurrentFocusWindowOnDefaultDisplay()) {
1110                     return;
1111                 }
1112 
1113                 WindowManager windowManager = (WindowManager)
1114                         mContext.getSystemService(Context.WINDOW_SERVICE);
1115                 windowManager.getDefaultDisplay().getRealSize(mTempPoint);
1116                 final int screenWidth = mTempPoint.x;
1117                 final int screenHeight = mTempPoint.y;
1118 
1119                 Region unaccountedSpace = mTempRegion;
1120                 unaccountedSpace.set(0, 0, screenWidth, screenHeight);
1121 
1122                 final SparseArray<WindowState> visibleWindows = mTempWindowStates;
1123                 populateVisibleWindowsOnScreenLocked(visibleWindows);
1124                 Set<IBinder> addedWindows = mTempBinderSet;
1125                 addedWindows.clear();
1126 
1127                 boolean focusedWindowAdded = false;
1128 
1129                 final int visibleWindowCount = visibleWindows.size();
1130                 HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>();
1131 
1132                 // Iterate until we figure out what is touchable for the entire screen.
1133                 for (int i = visibleWindowCount - 1; i >= 0; i--) {
1134                     final WindowState windowState = visibleWindows.valueAt(i);
1135 
1136                     final Rect boundsInScreen = mTempRect;
1137                     computeWindowBoundsInScreen(windowState, boundsInScreen);
1138 
1139                     if (windowMattersToAccessibility(windowState, boundsInScreen, unaccountedSpace,
1140                             skipRemainingWindowsForTasks)) {
1141                         addPopulatedWindowInfo(windowState, boundsInScreen, windows, addedWindows);
1142                         updateUnaccountedSpace(windowState, boundsInScreen, unaccountedSpace,
1143                                 skipRemainingWindowsForTasks);
1144                         focusedWindowAdded |= windowState.isFocused();
1145                     }
1146 
1147                     if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
1148                         break;
1149                     }
1150                 }
1151 
1152                 // Remove child/parent references to windows that were not added.
1153                 final int windowCount = windows.size();
1154                 for (int i = 0; i < windowCount; i++) {
1155                     WindowInfo window = windows.get(i);
1156                     if (!addedWindows.contains(window.parentToken)) {
1157                         window.parentToken = null;
1158                     }
1159                     if (window.childTokens != null) {
1160                         final int childTokenCount = window.childTokens.size();
1161                         for (int j = childTokenCount - 1; j >= 0; j--) {
1162                             if (!addedWindows.contains(window.childTokens.get(j))) {
1163                                 window.childTokens.remove(j);
1164                             }
1165                         }
1166                         // Leave the child token list if empty.
1167                     }
1168                 }
1169 
1170                 visibleWindows.clear();
1171                 addedWindows.clear();
1172 
1173                 if (!forceSend) {
1174                     // We computed the windows and if they changed notify the client.
1175                     if (mOldWindows.size() != windows.size()) {
1176                         // Different size means something changed.
1177                         windowsChanged = true;
1178                     } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) {
1179                         // Since we always traverse windows from high to low layer
1180                         // the old and new windows at the same index should be the
1181                         // same, otherwise something changed.
1182                         for (int i = 0; i < windowCount; i++) {
1183                             WindowInfo oldWindow = mOldWindows.get(i);
1184                             WindowInfo newWindow = windows.get(i);
1185                             // We do not care for layer changes given the window
1186                             // order does not change. This brings no new information
1187                             // to the clients.
1188                             if (windowChangedNoLayer(oldWindow, newWindow)) {
1189                                 windowsChanged = true;
1190                                 break;
1191                             }
1192                         }
1193                     }
1194                 }
1195 
1196                 if (forceSend || windowsChanged) {
1197                     cacheWindows(windows);
1198                 }
1199             }
1200 
1201             // Now we do not hold the lock, so send the windows over.
1202             if (forceSend || windowsChanged) {
1203                 if (DEBUG) {
1204                     Log.i(LOG_TAG, "Windows changed or force sending:" + windows);
1205                 }
1206                 mCallback.onWindowsForAccessibilityChanged(windows);
1207             } else {
1208                 if (DEBUG) {
1209                     Log.i(LOG_TAG, "No windows changed.");
1210                 }
1211             }
1212 
1213             // Recycle the windows as we do not need them.
1214             clearAndRecycleWindows(windows);
1215         }
1216 
windowMattersToAccessibility(WindowState windowState, Rect boundsInScreen, Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks)1217         private boolean windowMattersToAccessibility(WindowState windowState, Rect boundsInScreen,
1218                 Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
1219             if (windowState.isFocused()) {
1220                 return true;
1221             }
1222 
1223             // If the window is part of a task that we're finished with - ignore.
1224             final Task task = windowState.getTask();
1225             if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
1226                 return false;
1227             }
1228 
1229             // Ignore non-touchable windows, except the split-screen divider, which is
1230             // occasionally non-touchable but still useful for identifying split-screen
1231             // mode.
1232             if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
1233                     && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
1234                 return false;
1235             }
1236 
1237             // If the window is completely covered by other windows - ignore.
1238             if (unaccountedSpace.quickReject(boundsInScreen)) {
1239                 return false;
1240             }
1241 
1242             // Add windows of certain types not covered by modal windows.
1243             if (isReportedWindowType(windowState.mAttrs.type)) {
1244                 return true;
1245             }
1246 
1247             return false;
1248         }
1249 
updateUnaccountedSpace(WindowState windowState, Rect boundsInScreen, Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks)1250         private void updateUnaccountedSpace(WindowState windowState, Rect boundsInScreen,
1251                 Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
1252             if (windowState.mAttrs.type
1253                     != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
1254 
1255                 // Account for the space this window takes if the window
1256                 // is not an accessibility overlay which does not change
1257                 // the reported windows.
1258                 unaccountedSpace.op(boundsInScreen, unaccountedSpace,
1259                         Region.Op.REVERSE_DIFFERENCE);
1260 
1261                 // If a window is modal it prevents other windows from being touched
1262                 if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1263                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
1264                     // Account for all space in the task, whether the windows in it are
1265                     // touchable or not. The modal window blocks all touches from the task's
1266                     // area.
1267                     unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
1268                             Region.Op.REVERSE_DIFFERENCE);
1269 
1270                     final Task task = windowState.getTask();
1271                     if (task != null) {
1272                         // If the window is associated with a particular task, we can skip the
1273                         // rest of the windows for that task.
1274                         skipRemainingWindowsForTasks.add(task.mTaskId);
1275                     } else {
1276                         // If the window is not associated with a particular task, then it is
1277                         // globally modal. In this case we can skip all remaining windows.
1278                         unaccountedSpace.setEmpty();
1279                     }
1280                 }
1281             }
1282         }
1283 
computeWindowBoundsInScreen(WindowState windowState, Rect outBounds)1284         private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
1285             // Get the touchable frame.
1286             Region touchableRegion = mTempRegion1;
1287             windowState.getTouchableRegion(touchableRegion);
1288             Rect touchableFrame = mTempRect;
1289             touchableRegion.getBounds(touchableFrame);
1290 
1291             // Move to origin as all transforms are captured by the matrix.
1292             RectF windowFrame = mTempRectF;
1293             windowFrame.set(touchableFrame);
1294             windowFrame.offset(-windowState.getFrameLw().left, -windowState.getFrameLw().top);
1295 
1296             // Map the frame to get what appears on the screen.
1297             Matrix matrix = mTempMatrix;
1298             populateTransformationMatrixLocked(windowState, matrix);
1299             matrix.mapRect(windowFrame);
1300 
1301             // Got the bounds.
1302             outBounds.set((int) windowFrame.left, (int) windowFrame.top,
1303                     (int) windowFrame.right, (int) windowFrame.bottom);
1304         }
1305 
addPopulatedWindowInfo( WindowState windowState, Rect boundsInScreen, List<WindowInfo> out, Set<IBinder> tokenOut)1306         private static void addPopulatedWindowInfo(
1307                 WindowState windowState, Rect boundsInScreen,
1308                 List<WindowInfo> out, Set<IBinder> tokenOut) {
1309             final WindowInfo window = windowState.getWindowInfo();
1310             window.boundsInScreen.set(boundsInScreen);
1311             window.layer = tokenOut.size();
1312             out.add(window);
1313             tokenOut.add(window.token);
1314         }
1315 
cacheWindows(List<WindowInfo> windows)1316         private void cacheWindows(List<WindowInfo> windows) {
1317             final int oldWindowCount = mOldWindows.size();
1318             for (int i = oldWindowCount - 1; i >= 0; i--) {
1319                 mOldWindows.remove(i).recycle();
1320             }
1321             final int newWindowCount = windows.size();
1322             for (int i = 0; i < newWindowCount; i++) {
1323                 WindowInfo newWindow = windows.get(i);
1324                 mOldWindows.add(WindowInfo.obtain(newWindow));
1325             }
1326         }
1327 
windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow)1328         private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
1329             if (oldWindow == newWindow) {
1330                 return false;
1331             }
1332             if (oldWindow == null) {
1333                 return true;
1334             }
1335             if (newWindow == null) {
1336                 return true;
1337             }
1338             if (oldWindow.type != newWindow.type) {
1339                 return true;
1340             }
1341             if (oldWindow.focused != newWindow.focused) {
1342                 return true;
1343             }
1344             if (oldWindow.token == null) {
1345                 if (newWindow.token != null) {
1346                     return true;
1347                 }
1348             } else if (!oldWindow.token.equals(newWindow.token)) {
1349                 return true;
1350             }
1351             if (oldWindow.parentToken == null) {
1352                 if (newWindow.parentToken != null) {
1353                     return true;
1354                 }
1355             } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
1356                 return true;
1357             }
1358             if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
1359                 return true;
1360             }
1361             if (oldWindow.childTokens != null && newWindow.childTokens != null
1362                     && !oldWindow.childTokens.equals(newWindow.childTokens)) {
1363                 return true;
1364             }
1365             if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
1366                 return true;
1367             }
1368             if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
1369                 return true;
1370             }
1371             return false;
1372         }
1373 
clearAndRecycleWindows(List<WindowInfo> windows)1374         private static void clearAndRecycleWindows(List<WindowInfo> windows) {
1375             final int windowCount = windows.size();
1376             for (int i = windowCount - 1; i >= 0; i--) {
1377                 windows.remove(i).recycle();
1378             }
1379         }
1380 
isReportedWindowType(int windowType)1381         private static boolean isReportedWindowType(int windowType) {
1382             return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
1383                     && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
1384                     && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
1385                     && windowType != WindowManager.LayoutParams.TYPE_DRAG
1386                     && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
1387                     && windowType != WindowManager.LayoutParams.TYPE_POINTER
1388                     && windowType != TYPE_MAGNIFICATION_OVERLAY
1389                     && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
1390                     && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
1391                     && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
1392         }
1393 
populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows)1394         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
1395             final List<WindowState> tempWindowStatesList = new ArrayList<>();
1396             final DisplayContent dc = mService.getDefaultDisplayContentLocked();
1397             dc.forAllWindows((w) -> {
1398                 if (w.isVisibleLw()) {
1399                     tempWindowStatesList.add(w);
1400                 }
1401             }, false /* traverseTopToBottom */);
1402             // Insert the re-parented windows in another display on top of their parents in
1403             // default display.
1404             mService.mRoot.forAllWindows(w -> {
1405                 final WindowState parentWindow = findRootDisplayParentWindow(w);
1406                 if (parentWindow == null) {
1407                     return;
1408                 }
1409 
1410                 // TODO: Use Region instead to get rid of this complicated logic.
1411                 // Check the tap exclude region of the parent window. If the tap exclude region
1412                 // is empty, it means there is another can-receive-pointer-event view on top of
1413                 // the region. Hence, we don't count the window as visible.
1414                 if (w.isVisibleLw() && parentWindow.getDisplayContent().isDefaultDisplay
1415                         && parentWindow.hasTapExcludeRegion()
1416                         && tempWindowStatesList.contains(parentWindow)) {
1417                     tempWindowStatesList.add(
1418                             tempWindowStatesList.lastIndexOf(parentWindow) + 1, w);
1419                 }
1420             }, true /* traverseTopToBottom */);
1421             for (int i = 0; i < tempWindowStatesList.size(); i++) {
1422                 outWindows.put(i, tempWindowStatesList.get(i));
1423             }
1424         }
1425 
findRootDisplayParentWindow(WindowState win)1426         private WindowState findRootDisplayParentWindow(WindowState win) {
1427             WindowState displayParentWindow = win.getDisplayContent().getParentWindow();
1428             if (displayParentWindow == null) {
1429                 return null;
1430             }
1431             WindowState candidate = displayParentWindow;
1432             while (candidate != null) {
1433                 displayParentWindow = candidate;
1434                 candidate = displayParentWindow.getDisplayContent().getParentWindow();
1435             }
1436             return displayParentWindow;
1437         }
1438 
isCurrentFocusWindowOnDefaultDisplay()1439         private boolean isCurrentFocusWindowOnDefaultDisplay() {
1440             final WindowState focusedWindow =
1441                     mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
1442             if (focusedWindow == null) {
1443                 return false;
1444             }
1445 
1446             final WindowState rootDisplayParentWindow = findRootDisplayParentWindow(focusedWindow);
1447             if (!focusedWindow.isDefaultDisplay()
1448                     && (rootDisplayParentWindow == null
1449                     || !rootDisplayParentWindow.isDefaultDisplay())) {
1450                 return false;
1451             }
1452 
1453             return true;
1454         }
1455 
1456         private class MyHandler extends Handler {
1457             public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
1458 
MyHandler(Looper looper)1459             public MyHandler(Looper looper) {
1460                 super(looper, null, false);
1461             }
1462 
1463             @Override
1464             @SuppressWarnings("unchecked")
handleMessage(Message message)1465             public void handleMessage(Message message) {
1466                 switch (message.what) {
1467                     case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
1468                         computeChangedWindows(false);
1469                     } break;
1470                 }
1471             }
1472         }
1473     }
1474 }
1475