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.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
20 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
21 
22 import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
24 
25 import android.annotation.MainThread;
26 import android.annotation.Nullable;
27 import android.content.Context;
28 import android.graphics.Rect;
29 import android.hardware.display.DisplayManager;
30 import android.os.Handler;
31 import android.util.SparseArray;
32 import android.view.Display;
33 import android.view.SurfaceControl;
34 import android.view.WindowManagerGlobal;
35 import android.view.accessibility.AccessibilityManager;
36 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
37 import android.view.accessibility.IWindowMagnificationConnection;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
41 import com.android.systemui.CoreStartable;
42 import com.android.systemui.dagger.SysUISingleton;
43 import com.android.systemui.dagger.qualifiers.Main;
44 import com.android.systemui.model.SysUiState;
45 import com.android.systemui.recents.OverviewProxyService;
46 import com.android.systemui.settings.DisplayTracker;
47 import com.android.systemui.statusbar.CommandQueue;
48 import com.android.systemui.util.settings.SecureSettings;
49 
50 import java.io.PrintWriter;
51 
52 import javax.inject.Inject;
53 
54 /**
55  * Class to handle the interaction with
56  * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
57  * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
58  * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
59  */
60 @SysUISingleton
61 public class WindowMagnification implements CoreStartable, CommandQueue.Callbacks {
62     private static final String TAG = "WindowMagnification";
63 
64     private final ModeSwitchesController mModeSwitchesController;
65     private final Context mContext;
66     private final Handler mHandler;
67     private final AccessibilityManager mAccessibilityManager;
68     private final CommandQueue mCommandQueue;
69     private final OverviewProxyService mOverviewProxyService;
70     private final DisplayTracker mDisplayTracker;
71     private final AccessibilityLogger mA11yLogger;
72 
73     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
74     private SysUiState mSysUiState;
75 
76     @VisibleForTesting
77     SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
78 
79     private static class ControllerSupplier extends
80             DisplayIdIndexSupplier<WindowMagnificationController> {
81 
82         private final Context mContext;
83         private final Handler mHandler;
84         private final WindowMagnifierCallback mWindowMagnifierCallback;
85         private final SysUiState mSysUiState;
86         private final SecureSettings mSecureSettings;
87 
ControllerSupplier(Context context, Handler handler, WindowMagnifierCallback windowMagnifierCallback, DisplayManager displayManager, SysUiState sysUiState, SecureSettings secureSettings)88         ControllerSupplier(Context context, Handler handler,
89                 WindowMagnifierCallback windowMagnifierCallback,
90                 DisplayManager displayManager, SysUiState sysUiState,
91                 SecureSettings secureSettings) {
92             super(displayManager);
93             mContext = context;
94             mHandler = handler;
95             mWindowMagnifierCallback = windowMagnifierCallback;
96             mSysUiState = sysUiState;
97             mSecureSettings = secureSettings;
98         }
99 
100         @Override
createInstance(Display display)101         protected WindowMagnificationController createInstance(Display display) {
102             final Context windowContext = mContext.createWindowContext(display,
103                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
104             windowContext.setTheme(com.android.systemui.R.style.Theme_SystemUI);
105             return new WindowMagnificationController(
106                     windowContext,
107                     mHandler,
108                     new WindowMagnificationAnimationController(windowContext),
109                     new SfVsyncFrameCallbackProvider(),
110                     null,
111                     new SurfaceControl.Transaction(),
112                     mWindowMagnifierCallback,
113                     mSysUiState,
114                     WindowManagerGlobal::getWindowSession,
115                     mSecureSettings);
116         }
117     }
118 
119     @VisibleForTesting
120     DisplayIdIndexSupplier<WindowMagnificationController> mMagnificationControllerSupplier;
121 
122     private static class SettingsSupplier extends
123             DisplayIdIndexSupplier<MagnificationSettingsController> {
124 
125         private final Context mContext;
126         private final MagnificationSettingsController.Callback mSettingsControllerCallback;
127         private final SecureSettings mSecureSettings;
128 
SettingsSupplier(Context context, MagnificationSettingsController.Callback settingsControllerCallback, DisplayManager displayManager, SecureSettings secureSettings)129         SettingsSupplier(Context context,
130                 MagnificationSettingsController.Callback settingsControllerCallback,
131                 DisplayManager displayManager,
132                 SecureSettings secureSettings) {
133             super(displayManager);
134             mContext = context;
135             mSettingsControllerCallback = settingsControllerCallback;
136             mSecureSettings = secureSettings;
137         }
138 
139         @Override
createInstance(Display display)140         protected MagnificationSettingsController createInstance(Display display) {
141             final Context windowContext = mContext.createWindowContext(display,
142                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
143             windowContext.setTheme(com.android.systemui.R.style.Theme_SystemUI);
144             return new MagnificationSettingsController(
145                     windowContext,
146                     new SfVsyncFrameCallbackProvider(),
147                     mSettingsControllerCallback,
148                     mSecureSettings);
149         }
150     }
151 
152     @VisibleForTesting
153     DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
154 
155     @Inject
WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger)156     public WindowMagnification(Context context, @Main Handler mainHandler,
157             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
158             SysUiState sysUiState, OverviewProxyService overviewProxyService,
159             SecureSettings secureSettings, DisplayTracker displayTracker,
160             DisplayManager displayManager, AccessibilityLogger a11yLogger) {
161         mContext = context;
162         mHandler = mainHandler;
163         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
164         mCommandQueue = commandQueue;
165         mModeSwitchesController = modeSwitchesController;
166         mSysUiState = sysUiState;
167         mOverviewProxyService = overviewProxyService;
168         mDisplayTracker = displayTracker;
169         mA11yLogger = a11yLogger;
170         mMagnificationControllerSupplier = new ControllerSupplier(context,
171                 mHandler, mWindowMagnifierCallback,
172                 displayManager, sysUiState, secureSettings);
173         mMagnificationSettingsSupplier = new SettingsSupplier(context,
174                 mMagnificationSettingsControllerCallback, displayManager, secureSettings);
175 
176         mModeSwitchesController.setClickListenerDelegate(
177                 displayId -> mHandler.post(() -> {
178                     toggleSettingsPanelVisibility(displayId);
179                 }));
180     }
181 
182     @Override
start()183     public void start() {
184         mCommandQueue.addCallback(this);
185         mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
186             @Override
187             public void onConnectionChanged(boolean isConnected) {
188                 if (isConnected) {
189                     updateSysUiStateFlag();
190                 }
191             }
192         });
193     }
194 
updateSysUiStateFlag()195     private void updateSysUiStateFlag() {
196         //TODO(b/187510533): support multi-display once SysuiState supports it.
197         final WindowMagnificationController controller =
198                 mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId());
199         if (controller != null) {
200             controller.updateSysUIStateFlag();
201         } else {
202             // The instance is initialized when there is an IPC request. Considering
203             // self-crash cases, we need to reset the flag in such situation.
204             mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
205                     .commitUpdate(mDisplayTracker.getDefaultDisplayId());
206         }
207     }
208 
209     @MainThread
enableWindowMagnification(int displayId, float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable IRemoteMagnificationAnimationCallback callback)210     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
211             float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
212             @Nullable IRemoteMagnificationAnimationCallback callback) {
213         final WindowMagnificationController windowMagnificationController =
214                 mMagnificationControllerSupplier.get(displayId);
215         if (windowMagnificationController != null) {
216             windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
217                     magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
218         }
219     }
220 
221     @MainThread
setScale(int displayId, float scale)222     void setScale(int displayId, float scale) {
223         final WindowMagnificationController windowMagnificationController =
224                 mMagnificationControllerSupplier.get(displayId);
225         if (windowMagnificationController != null) {
226             windowMagnificationController.setScale(scale);
227         }
228     }
229 
230     @MainThread
moveWindowMagnifier(int displayId, float offsetX, float offsetY)231     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
232         final WindowMagnificationController windowMagnificationcontroller =
233                 mMagnificationControllerSupplier.get(displayId);
234         if (windowMagnificationcontroller != null) {
235             windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
236         }
237     }
238 
239     @MainThread
moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY, IRemoteMagnificationAnimationCallback callback)240     void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
241             IRemoteMagnificationAnimationCallback callback) {
242         final WindowMagnificationController windowMagnificationController =
243                 mMagnificationControllerSupplier.get(displayId);
244         if (windowMagnificationController != null) {
245             windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
246                     callback);
247         }
248     }
249 
250     @MainThread
disableWindowMagnification(int displayId, @Nullable IRemoteMagnificationAnimationCallback callback)251     void disableWindowMagnification(int displayId,
252             @Nullable IRemoteMagnificationAnimationCallback callback) {
253         final WindowMagnificationController windowMagnificationController =
254                 mMagnificationControllerSupplier.get(displayId);
255         if (windowMagnificationController != null) {
256             windowMagnificationController.deleteWindowMagnification(callback);
257         }
258     }
259 
260     @MainThread
toggleSettingsPanelVisibility(int displayId)261     void toggleSettingsPanelVisibility(int displayId) {
262         final MagnificationSettingsController magnificationSettingsController =
263                 mMagnificationSettingsSupplier.get(displayId);
264         if (magnificationSettingsController != null) {
265             magnificationSettingsController.toggleSettingsPanelVisibility();
266         }
267     }
268 
269     @MainThread
hideMagnificationSettingsPanel(int displayId)270     void hideMagnificationSettingsPanel(int displayId) {
271         final MagnificationSettingsController magnificationSettingsController =
272                 mMagnificationSettingsSupplier.get(displayId);
273         if (magnificationSettingsController != null) {
274             magnificationSettingsController.closeMagnificationSettings();
275         }
276     }
277 
isMagnificationSettingsPanelShowing(int displayId)278     boolean isMagnificationSettingsPanelShowing(int displayId) {
279         final MagnificationSettingsController magnificationSettingsController =
280                 mMagnificationSettingsSupplier.get(displayId);
281         if (magnificationSettingsController != null) {
282             return magnificationSettingsController.isMagnificationSettingsShowing();
283         }
284         return false;
285     }
286 
287     @MainThread
showMagnificationButton(int displayId, int magnificationMode)288     void showMagnificationButton(int displayId, int magnificationMode) {
289         // not to show mode switch button if settings panel is already showing to
290         // prevent settings panel be covered by the button.
291         if (isMagnificationSettingsPanelShowing(displayId)) {
292             return;
293         }
294         mModeSwitchesController.showButton(displayId, magnificationMode);
295     }
296 
297     @MainThread
removeMagnificationButton(int displayId)298     void removeMagnificationButton(int displayId) {
299         mModeSwitchesController.removeButton(displayId);
300     }
301 
302     @MainThread
setUserMagnificationScale(int userId, int displayId, float scale)303     void setUserMagnificationScale(int userId, int displayId, float scale) {
304         SparseArray<Float> scales = mUsersScales.get(userId);
305         if (scales == null) {
306             scales = new SparseArray<>();
307             mUsersScales.put(userId, scales);
308         }
309         if (scales.contains(displayId) && scales.get(displayId) == scale) {
310             return;
311         }
312         scales.put(displayId, scale);
313 
314         final MagnificationSettingsController magnificationSettingsController =
315                 mMagnificationSettingsSupplier.get(displayId);
316         magnificationSettingsController.setMagnificationScale(scale);
317     }
318 
319     @VisibleForTesting
320     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
321         @Override
322         public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
323             if (mWindowMagnificationConnectionImpl != null) {
324                 mWindowMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
325             }
326         }
327 
328         @Override
329         public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
330             if (mWindowMagnificationConnectionImpl != null) {
331                 mWindowMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
332             }
333         }
334 
335         @Override
336         public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
337             if (mWindowMagnificationConnectionImpl != null) {
338                 mWindowMagnificationConnectionImpl.onPerformScaleAction(
339                         displayId, scale, updatePersistence);
340             }
341         }
342 
343         @Override
344         public void onAccessibilityActionPerformed(int displayId) {
345             if (mWindowMagnificationConnectionImpl != null) {
346                 mWindowMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
347             }
348         }
349 
350         @Override
351         public void onMove(int displayId) {
352             if (mWindowMagnificationConnectionImpl != null) {
353                 mWindowMagnificationConnectionImpl.onMove(displayId);
354             }
355         }
356 
357         @Override
358         public void onClickSettingsButton(int displayId) {
359             mHandler.post(() -> {
360                 toggleSettingsPanelVisibility(displayId);
361             });
362         }
363     };
364 
365     @VisibleForTesting
366     final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
367             new MagnificationSettingsController.Callback() {
368         @Override
369         public void onSetMagnifierSize(int displayId, int index) {
370             mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
371             mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED);
372         }
373 
374         @Override
375         public void onSetDiagonalScrolling(int displayId, boolean enable) {
376             mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
377         }
378 
379         @Override
380         public void onEditMagnifierSizeMode(int displayId, boolean enable) {
381             mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
382             mA11yLogger.log(enable
383                     ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
384                     : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
385         }
386 
387         @Override
388         public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) {
389             if (mWindowMagnificationConnectionImpl != null) {
390                 mWindowMagnificationConnectionImpl.onPerformScaleAction(
391                         displayId, scale, updatePersistence);
392             }
393         }
394 
395         @Override
396         public void onModeSwitch(int displayId, int newMode) {
397             mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
398         }
399 
400         @Override
401         public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
402             mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
403             mA11yLogger.log(shown
404                     ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED
405                     : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
406         }
407     };
408 
409     @MainThread
onSetMagnifierSizeInternal(int displayId, int index)410     private void onSetMagnifierSizeInternal(int displayId, int index) {
411         final WindowMagnificationController windowMagnificationController =
412                 mMagnificationControllerSupplier.get(displayId);
413         if (windowMagnificationController != null) {
414             windowMagnificationController.changeMagnificationSize(index);
415         }
416     }
417 
418     @MainThread
onSetDiagonalScrollingInternal(int displayId, boolean enable)419     private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
420         final WindowMagnificationController windowMagnificationController =
421                 mMagnificationControllerSupplier.get(displayId);
422         if (windowMagnificationController != null) {
423             windowMagnificationController.setDiagonalScrolling(enable);
424         }
425     }
426 
427     @MainThread
onEditMagnifierSizeModeInternal(int displayId, boolean enable)428     private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
429         final WindowMagnificationController windowMagnificationController =
430                 mMagnificationControllerSupplier.get(displayId);
431         if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
432             windowMagnificationController.setEditMagnifierSizeMode(enable);
433         }
434     }
435 
436     @MainThread
onModeSwitchInternal(int displayId, int newMode)437     private void onModeSwitchInternal(int displayId, int newMode) {
438         final WindowMagnificationController windowMagnificationController =
439                 mMagnificationControllerSupplier.get(displayId);
440         final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
441         final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
442         final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
443         if (changed) {
444             final MagnificationSettingsController magnificationSettingsController =
445                     mMagnificationSettingsSupplier.get(displayId);
446             if (magnificationSettingsController != null) {
447                 magnificationSettingsController.closeMagnificationSettings();
448             }
449             if (mWindowMagnificationConnectionImpl != null) {
450                 mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
451             }
452         }
453     }
454 
455     @MainThread
onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown)456     private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
457         final WindowMagnificationController windowMagnificationController =
458                 mMagnificationControllerSupplier.get(displayId);
459         if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
460             windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
461         }
462     }
463 
464     @Override
requestWindowMagnificationConnection(boolean connect)465     public void requestWindowMagnificationConnection(boolean connect) {
466         if (connect) {
467             setWindowMagnificationConnection();
468         } else {
469             clearWindowMagnificationConnection();
470         }
471     }
472 
473     @Override
dump(PrintWriter pw, String[] args)474     public void dump(PrintWriter pw, String[] args) {
475         pw.println(TAG);
476         mMagnificationControllerSupplier.forEach(
477                 magnificationController -> magnificationController.dump(pw));
478     }
479 
setWindowMagnificationConnection()480     private void setWindowMagnificationConnection() {
481         if (mWindowMagnificationConnectionImpl == null) {
482             mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this,
483                     mHandler);
484         }
485         mAccessibilityManager.setWindowMagnificationConnection(
486                 mWindowMagnificationConnectionImpl);
487     }
488 
clearWindowMagnificationConnection()489     private void clearWindowMagnificationConnection() {
490         mAccessibilityManager.setWindowMagnificationConnection(null);
491         //TODO: destroy controllers.
492     }
493 }
494