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