/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.accessibility; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; import android.view.Display; import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import java.io.FileDescriptor; import java.io.PrintWriter; import javax.inject.Inject; /** * Class to handle the interaction with * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)} * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton public class WindowMagnification extends SystemUI implements WindowMagnifierCallback, CommandQueue.Callbacks { private static final String TAG = "WindowMagnification"; private final ModeSwitchesController mModeSwitchesController; private final Handler mHandler; private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; private final OverviewProxyService mOverviewProxyService; private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; private Configuration mLastConfiguration; private SysUiState mSysUiState; private static class AnimationControllerSupplier extends DisplayIdIndexSupplier { private final Context mContext; private final Handler mHandler; private final WindowMagnifierCallback mWindowMagnifierCallback; private final SysUiState mSysUiState; AnimationControllerSupplier(Context context, Handler handler, WindowMagnifierCallback windowMagnifierCallback, DisplayManager displayManager, SysUiState sysUiState) { super(displayManager); mContext = context; mHandler = handler; mWindowMagnifierCallback = windowMagnifierCallback; mSysUiState = sysUiState; } @Override protected WindowMagnificationAnimationController createInstance(Display display) { final Context windowContext = mContext.createWindowContext(display, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null); final WindowMagnificationController controller = new WindowMagnificationController( windowContext, mHandler, new SfVsyncFrameCallbackProvider(), null, new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState); return new WindowMagnificationAnimationController(windowContext, controller); } } @VisibleForTesting DisplayIdIndexSupplier mAnimationControllerSupplier; @Inject public WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService) { super(context); mHandler = mainHandler; mLastConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; mModeSwitchesController = modeSwitchesController; mSysUiState = sysUiState; mOverviewProxyService = overviewProxyService; mAnimationControllerSupplier = new AnimationControllerSupplier(context, mHandler, this, context.getSystemService(DisplayManager.class), sysUiState); } @Override public void onConfigurationChanged(Configuration newConfig) { final int configDiff = newConfig.diff(mLastConfiguration); mLastConfiguration.setTo(newConfig); mAnimationControllerSupplier.forEach( animationController -> animationController.onConfigurationChanged(configDiff)); if (mModeSwitchesController != null) { mModeSwitchesController.onConfigurationChanged(configDiff); } } @Override public void start() { mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { if (isConnected) { updateSysUiStateFlag(); } } }); } private void updateSysUiStateFlag() { //TODO(b/187510533): support multi-display once SysuiState supports it. final WindowMagnificationAnimationController controller = mAnimationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY); if (controller != null) { controller.updateSysUiStateFlag(); } else { // The instance is initialized when there is an IPC request. Considering // self-crash cases, we need to reset the flag in such situation. mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false) .commitUpdate(Display.DEFAULT_DISPLAY); } } @MainThread void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable IRemoteMagnificationAnimationCallback callback) { final WindowMagnificationAnimationController windowMagnificationAnimationController = mAnimationControllerSupplier.get(displayId); if (windowMagnificationAnimationController != null) { windowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY, callback); } } @MainThread void setScale(int displayId, float scale) { final WindowMagnificationAnimationController windowMagnificationAnimationController = mAnimationControllerSupplier.get(displayId); if (windowMagnificationAnimationController != null) { windowMagnificationAnimationController.setScale(scale); } } @MainThread void moveWindowMagnifier(int displayId, float offsetX, float offsetY) { final WindowMagnificationAnimationController windowMagnificationAnimationController = mAnimationControllerSupplier.get(displayId); if (windowMagnificationAnimationController != null) { windowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY); } } @MainThread void disableWindowMagnification(int displayId, @Nullable IRemoteMagnificationAnimationCallback callback) { final WindowMagnificationAnimationController windowMagnificationAnimationController = mAnimationControllerSupplier.get(displayId); if (windowMagnificationAnimationController != null) { windowMagnificationAnimationController.deleteWindowMagnification(callback); } } @Override public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) { if (mWindowMagnificationConnectionImpl != null) { mWindowMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame); } } @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { if (mWindowMagnificationConnectionImpl != null) { mWindowMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds); } } @Override public void onPerformScaleAction(int displayId, float scale) { if (mWindowMagnificationConnectionImpl != null) { mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale); } } @Override public void onAccessibilityActionPerformed(int displayId) { if (mWindowMagnificationConnectionImpl != null) { mWindowMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId); } } @Override public void requestWindowMagnificationConnection(boolean connect) { if (connect) { setWindowMagnificationConnection(); } else { clearWindowMagnificationConnection(); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(TAG); mAnimationControllerSupplier.forEach( animationController -> animationController.dump(pw)); } private void setWindowMagnificationConnection() { if (mWindowMagnificationConnectionImpl == null) { mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this, mHandler, mModeSwitchesController); } mAccessibilityManager.setWindowMagnificationConnection( mWindowMagnificationConnectionImpl); } private void clearWindowMagnificationConnection() { mAccessibilityManager.setWindowMagnificationConnection(null); //TODO: destroy controllers. } }