1 /*
2  * Copyright (C) 2019 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.systemui.accessibility;
18 
19 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
20 
21 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
22 
23 import android.annotation.MainThread;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.res.Configuration;
27 import android.graphics.Rect;
28 import android.hardware.display.DisplayManager;
29 import android.os.Handler;
30 import android.view.Display;
31 import android.view.SurfaceControl;
32 import android.view.accessibility.AccessibilityManager;
33 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
34 import android.view.accessibility.IWindowMagnificationConnection;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
38 import com.android.systemui.SystemUI;
39 import com.android.systemui.dagger.SysUISingleton;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.model.SysUiState;
42 import com.android.systemui.recents.OverviewProxyService;
43 import com.android.systemui.statusbar.CommandQueue;
44 
45 import java.io.FileDescriptor;
46 import java.io.PrintWriter;
47 
48 import javax.inject.Inject;
49 
50 /**
51  * Class to handle the interaction with
52  * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
53  * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
54  * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
55  */
56 @SysUISingleton
57 public class WindowMagnification extends SystemUI implements WindowMagnifierCallback,
58         CommandQueue.Callbacks {
59     private static final String TAG = "WindowMagnification";
60 
61     private final ModeSwitchesController mModeSwitchesController;
62     private final Handler mHandler;
63     private final AccessibilityManager mAccessibilityManager;
64     private final CommandQueue mCommandQueue;
65     private final OverviewProxyService mOverviewProxyService;
66 
67     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
68     private Configuration mLastConfiguration;
69     private SysUiState mSysUiState;
70 
71     private static class AnimationControllerSupplier extends
72             DisplayIdIndexSupplier<WindowMagnificationAnimationController> {
73 
74         private final Context mContext;
75         private final Handler mHandler;
76         private final WindowMagnifierCallback mWindowMagnifierCallback;
77         private final SysUiState mSysUiState;
78 
AnimationControllerSupplier(Context context, Handler handler, WindowMagnifierCallback windowMagnifierCallback, DisplayManager displayManager, SysUiState sysUiState)79         AnimationControllerSupplier(Context context, Handler handler,
80                 WindowMagnifierCallback windowMagnifierCallback,
81                 DisplayManager displayManager, SysUiState sysUiState) {
82             super(displayManager);
83             mContext = context;
84             mHandler = handler;
85             mWindowMagnifierCallback = windowMagnifierCallback;
86             mSysUiState = sysUiState;
87         }
88 
89         @Override
createInstance(Display display)90         protected WindowMagnificationAnimationController createInstance(Display display) {
91             final Context windowContext = mContext.createWindowContext(display,
92                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
93             final WindowMagnificationController controller = new WindowMagnificationController(
94                     windowContext,
95                     mHandler, new SfVsyncFrameCallbackProvider(), null,
96                     new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState);
97             return new WindowMagnificationAnimationController(windowContext, controller);
98         }
99     }
100 
101     @VisibleForTesting
102     DisplayIdIndexSupplier<WindowMagnificationAnimationController> mAnimationControllerSupplier;
103 
104     @Inject
WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService)105     public WindowMagnification(Context context, @Main Handler mainHandler,
106             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
107             SysUiState sysUiState, OverviewProxyService overviewProxyService) {
108         super(context);
109         mHandler = mainHandler;
110         mLastConfiguration = new Configuration(context.getResources().getConfiguration());
111         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
112         mCommandQueue = commandQueue;
113         mModeSwitchesController = modeSwitchesController;
114         mSysUiState = sysUiState;
115         mOverviewProxyService = overviewProxyService;
116         mAnimationControllerSupplier = new AnimationControllerSupplier(context,
117                 mHandler, this, context.getSystemService(DisplayManager.class), sysUiState);
118     }
119 
120     @Override
onConfigurationChanged(Configuration newConfig)121     public void onConfigurationChanged(Configuration newConfig) {
122         final int configDiff = newConfig.diff(mLastConfiguration);
123         mLastConfiguration.setTo(newConfig);
124         mAnimationControllerSupplier.forEach(
125                 animationController -> animationController.onConfigurationChanged(configDiff));
126         if (mModeSwitchesController != null) {
127             mModeSwitchesController.onConfigurationChanged(configDiff);
128         }
129     }
130 
131     @Override
start()132     public void start() {
133         mCommandQueue.addCallback(this);
134         mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
135             @Override
136             public void onConnectionChanged(boolean isConnected) {
137                 if (isConnected) {
138                     updateSysUiStateFlag();
139                 }
140             }
141         });
142     }
143 
updateSysUiStateFlag()144     private void updateSysUiStateFlag() {
145         //TODO(b/187510533): support multi-display once SysuiState supports it.
146         final WindowMagnificationAnimationController controller =
147                 mAnimationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY);
148         if (controller != null) {
149             controller.updateSysUiStateFlag();
150         } else {
151             // The instance is initialized when there is an IPC request. Considering
152             // self-crash cases, we need to reset the flag in such situation.
153             mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
154                     .commitUpdate(Display.DEFAULT_DISPLAY);
155         }
156     }
157 
158     @MainThread
enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable IRemoteMagnificationAnimationCallback callback)159     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
160             @Nullable IRemoteMagnificationAnimationCallback callback) {
161         final WindowMagnificationAnimationController windowMagnificationAnimationController =
162                 mAnimationControllerSupplier.get(displayId);
163         if (windowMagnificationAnimationController != null) {
164             windowMagnificationAnimationController.enableWindowMagnification(scale, centerX,
165                     centerY, callback);
166         }
167     }
168 
169     @MainThread
setScale(int displayId, float scale)170     void setScale(int displayId, float scale) {
171         final WindowMagnificationAnimationController windowMagnificationAnimationController =
172                 mAnimationControllerSupplier.get(displayId);
173         if (windowMagnificationAnimationController != null) {
174             windowMagnificationAnimationController.setScale(scale);
175         }
176     }
177 
178     @MainThread
moveWindowMagnifier(int displayId, float offsetX, float offsetY)179     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
180         final WindowMagnificationAnimationController windowMagnificationAnimationController =
181                 mAnimationControllerSupplier.get(displayId);
182         if (windowMagnificationAnimationController != null) {
183             windowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY);
184         }
185     }
186 
187     @MainThread
disableWindowMagnification(int displayId, @Nullable IRemoteMagnificationAnimationCallback callback)188     void disableWindowMagnification(int displayId,
189             @Nullable IRemoteMagnificationAnimationCallback callback) {
190         final WindowMagnificationAnimationController windowMagnificationAnimationController =
191                 mAnimationControllerSupplier.get(displayId);
192         if (windowMagnificationAnimationController != null) {
193             windowMagnificationAnimationController.deleteWindowMagnification(callback);
194         }
195     }
196 
197     @Override
onWindowMagnifierBoundsChanged(int displayId, Rect frame)198     public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
199         if (mWindowMagnificationConnectionImpl != null) {
200             mWindowMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
201         }
202     }
203 
204     @Override
onSourceBoundsChanged(int displayId, Rect sourceBounds)205     public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
206         if (mWindowMagnificationConnectionImpl != null) {
207             mWindowMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
208         }
209     }
210 
211     @Override
onPerformScaleAction(int displayId, float scale)212     public void onPerformScaleAction(int displayId, float scale) {
213         if (mWindowMagnificationConnectionImpl != null) {
214             mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale);
215         }
216     }
217 
218     @Override
onAccessibilityActionPerformed(int displayId)219     public void onAccessibilityActionPerformed(int displayId) {
220         if (mWindowMagnificationConnectionImpl != null) {
221             mWindowMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
222         }
223     }
224 
225     @Override
requestWindowMagnificationConnection(boolean connect)226     public void requestWindowMagnificationConnection(boolean connect) {
227         if (connect) {
228             setWindowMagnificationConnection();
229         } else {
230             clearWindowMagnificationConnection();
231         }
232     }
233 
234     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)235     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
236         pw.println(TAG);
237         mAnimationControllerSupplier.forEach(
238                 animationController -> animationController.dump(pw));
239     }
240 
setWindowMagnificationConnection()241     private void setWindowMagnificationConnection() {
242         if (mWindowMagnificationConnectionImpl == null) {
243             mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this,
244                     mHandler, mModeSwitchesController);
245         }
246         mAccessibilityManager.setWindowMagnificationConnection(
247                 mWindowMagnificationConnectionImpl);
248     }
249 
clearWindowMagnificationConnection()250     private void clearWindowMagnificationConnection() {
251         mAccessibilityManager.setWindowMagnificationConnection(null);
252         //TODO: destroy controllers.
253     }
254 }
255