diff options
Diffstat (limited to 'libs')
13 files changed, 2058 insertions, 12 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java new file mode 100644 index 000000000000..3263f79888d6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -0,0 +1,109 @@ +/* + * 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.wm.shell.common; + +import android.os.Handler; +import android.os.RemoteException; +import android.view.IDisplayWindowRotationCallback; +import android.view.IDisplayWindowRotationController; +import android.view.IWindowManager; +import android.window.WindowContainerTransaction; + +import java.util.ArrayList; + +/** + * This module deals with display rotations coming from WM. When WM starts a rotation: after it has + * frozen the screen, it will call into this class. This will then call all registered local + * controllers and give them a chance to queue up task changes to be applied synchronously with that + * rotation. + */ +public class DisplayChangeController { + + private final Handler mHandler; + private final IWindowManager mWmService; + + private final ArrayList<OnDisplayChangingListener> mRotationListener = + new ArrayList<>(); + private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>(); + + private final IDisplayWindowRotationController mDisplayRotationController = + new IDisplayWindowRotationController.Stub() { + @Override + public void onRotateDisplay(int displayId, final int fromRotation, + final int toRotation, IDisplayWindowRotationCallback callback) { + mHandler.post(() -> { + WindowContainerTransaction t = new WindowContainerTransaction(); + synchronized (mRotationListener) { + mTmpListeners.clear(); + // Make a local copy in case the handlers add/remove themselves. + mTmpListeners.addAll(mRotationListener); + } + for (OnDisplayChangingListener c : mTmpListeners) { + c.onRotateDisplay(displayId, fromRotation, toRotation, t); + } + try { + callback.continueRotateDisplay(toRotation, t); + } catch (RemoteException e) { + } + }); + } + }; + + public DisplayChangeController(Handler mainHandler, IWindowManager wmService) { + mHandler = mainHandler; + mWmService = wmService; + try { + mWmService.setDisplayWindowRotationController(mDisplayRotationController); + } catch (RemoteException e) { + throw new RuntimeException("Unable to register rotation controller"); + } + } + + /** + * Adds a display rotation controller. + */ + public void addRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.add(listener); + } + } + + /** + * Removes a display rotation controller. + */ + public void removeRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.remove(listener); + } + } + + /** + * Give a listener a chance to queue up configuration changes to execute as part of a + * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. + */ + public interface OnDisplayChangingListener { + /** + * Called before the display is rotated. Contents of this method must run synchronously. + * @param displayId Id of display that is rotating. + * @param fromRotation starting rotation of the display. + * @param toRotation target rotation of the display (after rotating). + * @param t A task transaction to populate. + */ + void onRotateDisplay(int displayId, int fromRotation, int toRotation, + WindowContainerTransaction t); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java new file mode 100644 index 000000000000..418973204add --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -0,0 +1,273 @@ +/* + * 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.wm.shell.common; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.IDisplayWindowListener; +import android.view.IWindowManager; + +import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener; + +import java.util.ArrayList; + +/** + * This module deals with display rotations coming from WM. When WM starts a rotation: after it has + * frozen the screen, it will call into this class. This will then call all registered local + * controllers and give them a chance to queue up task changes to be applied synchronously with that + * rotation. + */ +public class DisplayController { + private static final String TAG = "DisplayController"; + + private final Handler mHandler; + private final Context mContext; + private final IWindowManager mWmService; + private final DisplayChangeController mChangeController; + + private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>(); + private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>(); + + /** + * Gets a display by id from DisplayManager. + */ + public Display getDisplay(int displayId) { + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + return displayManager.getDisplay(displayId); + } + + private final IDisplayWindowListener mDisplayContainerListener = + new IDisplayWindowListener.Stub() { + @Override + public void onDisplayAdded(int displayId) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) != null) { + return; + } + Display display = getDisplay(displayId); + if (display == null) { + // It's likely that the display is private to some app and thus not + // accessible by system-ui. + return; + } + DisplayRecord record = new DisplayRecord(); + record.mDisplayId = displayId; + record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext + : mContext.createDisplayContext(display); + record.mDisplayLayout = new DisplayLayout(record.mContext, display); + mDisplays.put(displayId, record); + for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { + mDisplayChangedListeners.get(i).onDisplayAdded(displayId); + } + } + }); + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + mHandler.post(() -> { + synchronized (mDisplays) { + DisplayRecord dr = mDisplays.get(displayId); + if (dr == null) { + Slog.w(TAG, "Skipping Display Configuration change on non-added" + + " display."); + return; + } + Display display = getDisplay(displayId); + if (display == null) { + Slog.w(TAG, "Skipping Display Configuration change on invalid" + + " display. It may have been removed."); + return; + } + Context perDisplayContext = mContext; + if (displayId != Display.DEFAULT_DISPLAY) { + perDisplayContext = mContext.createDisplayContext(display); + } + dr.mContext = perDisplayContext.createConfigurationContext(newConfig); + dr.mDisplayLayout = new DisplayLayout(dr.mContext, display); + for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { + mDisplayChangedListeners.get(i).onDisplayConfigurationChanged( + displayId, newConfig); + } + } + }); + } + + @Override + public void onDisplayRemoved(int displayId) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null) { + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onDisplayRemoved(displayId); + } + mDisplays.remove(displayId); + } + }); + } + + @Override + public void onFixedRotationStarted(int displayId, int newRotation) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onFixedRotationStarted on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onFixedRotationStarted( + displayId, newRotation); + } + } + }); + } + + @Override + public void onFixedRotationFinished(int displayId) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onFixedRotationFinished on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId); + } + } + }); + } + }; + + public DisplayController(Context context, Handler handler, + IWindowManager wmService) { + mHandler = handler; + mContext = context; + mWmService = wmService; + mChangeController = new DisplayChangeController(mHandler, mWmService); + try { + mWmService.registerDisplayWindowListener(mDisplayContainerListener); + } catch (RemoteException e) { + throw new RuntimeException("Unable to register hierarchy listener"); + } + } + + /** + * Gets the DisplayLayout associated with a display. + */ + public @Nullable DisplayLayout getDisplayLayout(int displayId) { + final DisplayRecord r = mDisplays.get(displayId); + return r != null ? r.mDisplayLayout : null; + } + + /** + * Gets a display-specific context for a display. + */ + public @Nullable Context getDisplayContext(int displayId) { + final DisplayRecord r = mDisplays.get(displayId); + return r != null ? r.mContext : null; + } + + /** + * Add a display window-container listener. It will get notified whenever a display's + * configuration changes or when displays are added/removed from the WM hierarchy. + */ + public void addDisplayWindowListener(OnDisplaysChangedListener listener) { + synchronized (mDisplays) { + if (mDisplayChangedListeners.contains(listener)) { + return; + } + mDisplayChangedListeners.add(listener); + for (int i = 0; i < mDisplays.size(); ++i) { + listener.onDisplayAdded(mDisplays.keyAt(i)); + } + } + } + + /** + * Remove a display window-container listener. + */ + public void removeDisplayWindowListener(OnDisplaysChangedListener listener) { + synchronized (mDisplays) { + mDisplayChangedListeners.remove(listener); + } + } + + /** + * Adds a display rotation controller. + */ + public void addDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.addRotationListener(controller); + } + + /** + * Removes a display rotation controller. + */ + public void removeDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.removeRotationListener(controller); + } + + private static class DisplayRecord { + int mDisplayId; + Context mContext; + DisplayLayout mDisplayLayout; + } + + /** + * Gets notified when a display is added/removed to the WM hierarchy and when a display's + * window-configuration changes. + * + * @see IDisplayWindowListener + */ + public interface OnDisplaysChangedListener { + /** + * Called when a display has been added to the WM hierarchy. + */ + default void onDisplayAdded(int displayId) {} + + /** + * Called when a display's window-container configuration changes. + */ + default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {} + + /** + * Called when a display is removed. + */ + default void onDisplayRemoved(int displayId) {} + + /** + * Called when fixed rotation on a display is started. + */ + default void onFixedRotationStarted(int displayId, int newRotation) {} + + /** + * Called when fixed rotation on a display is finished. + */ + default void onFixedRotationFinished(int displayId) {} + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java new file mode 100644 index 000000000000..338ece5afbc2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -0,0 +1,493 @@ +/* + * 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.wm.shell.common; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.IntDef; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; +import android.view.InsetsSource; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +import com.android.internal.view.IInputMethodManager; + +import java.util.ArrayList; + +/** + * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. + */ +public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { + private static final String TAG = "DisplayImeController"; + + private static final boolean DEBUG = false; + + // NOTE: All these constants came from InsetsController. + public static final int ANIMATION_DURATION_SHOW_MS = 275; + public static final int ANIMATION_DURATION_HIDE_MS = 340; + public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + private static final int DIRECTION_NONE = 0; + private static final int DIRECTION_SHOW = 1; + private static final int DIRECTION_HIDE = 2; + private static final int FLOATING_IME_BOTTOM_INSET = -80; + + protected final IWindowManager mWmService; + protected final Handler mHandler; + private final TransactionPool mTransactionPool; + private final DisplayController mDisplayController; + private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); + private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); + + public DisplayImeController(IWindowManager wmService, DisplayController displayController, + Handler mainHandler, TransactionPool transactionPool) { + mHandler = mainHandler; + mWmService = wmService; + mTransactionPool = transactionPool; + mDisplayController = displayController; + mDisplayController.addDisplayWindowListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + // Add's a system-ui window-manager specifically for ime. This type is special because + // WM will defer IME inset handling to it in multi-window scenarious. + PerDisplay pd = new PerDisplay(displayId, + mDisplayController.getDisplayLayout(displayId).rotation()); + try { + mWmService.setDisplayWindowInsetsController(displayId, pd); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to set insets controller on display " + displayId); + } + mImePerDisplay.put(displayId, pd); + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + PerDisplay pd = mImePerDisplay.get(displayId); + if (pd == null) { + return; + } + if (mDisplayController.getDisplayLayout(displayId).rotation() + != pd.mRotation && isImeShowing(displayId)) { + pd.startAnimation(true, false /* forceRestart */); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + try { + mWmService.setDisplayWindowInsetsController(displayId, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to remove insets controller on display " + displayId); + } + mImePerDisplay.remove(displayId); + } + + private boolean isImeShowing(int displayId) { + PerDisplay pd = mImePerDisplay.get(displayId); + if (pd == null) { + return false; + } + final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME); + return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); + } + + private void dispatchPositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImePositionChanged(displayId, imeTop, t); + } + } + } + + @ImePositionProcessor.ImeAnimationFlags + private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean show, boolean isFloating, SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + int flags = 0; + for (ImePositionProcessor pp : mPositionProcessors) { + flags |= pp.onImeStartPositioning( + displayId, hiddenTop, shownTop, show, isFloating, t); + } + return flags; + } + } + + private void dispatchEndPositioning(int displayId, boolean cancel, + SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImeEndPositioning(displayId, cancel, t); + } + } + } + + /** + * Adds an {@link ImePositionProcessor} to be called during ime position updates. + */ + public void addPositionProcessor(ImePositionProcessor processor) { + synchronized (mPositionProcessors) { + if (mPositionProcessors.contains(processor)) { + return; + } + mPositionProcessors.add(processor); + } + } + + /** + * Removes an {@link ImePositionProcessor} to be called during ime position updates. + */ + public void removePositionProcessor(ImePositionProcessor processor) { + synchronized (mPositionProcessors) { + mPositionProcessors.remove(processor); + } + } + + class PerDisplay extends IDisplayWindowInsetsController.Stub { + final int mDisplayId; + final InsetsState mInsetsState = new InsetsState(); + InsetsSourceControl mImeSourceControl = null; + int mAnimationDirection = DIRECTION_NONE; + ValueAnimator mAnimation = null; + int mRotation = Surface.ROTATION_0; + boolean mImeShowing = false; + final Rect mImeFrame = new Rect(); + boolean mAnimateAlpha = true; + + PerDisplay(int displayId, int initialRotation) { + mDisplayId = displayId; + mRotation = initialRotation; + } + + @Override + public void insetsChanged(InsetsState insetsState) { + mHandler.post(() -> { + if (mInsetsState.equals(insetsState)) { + return; + } + + final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME); + final Rect newFrame = newSource.getFrame(); + final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame(); + + mInsetsState.set(insetsState, true /* copySources */); + if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { + if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); + startAnimation(mImeShowing, true /* forceRestart */); + } + }); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsChanged(insetsState); + if (activeControls != null) { + for (InsetsSourceControl activeControl : activeControls) { + if (activeControl == null) { + continue; + } + if (activeControl.getType() == InsetsState.ITYPE_IME) { + mHandler.post(() -> { + final Point lastSurfacePosition = mImeSourceControl != null + ? mImeSourceControl.getSurfacePosition() : null; + mImeSourceControl = activeControl; + if (!activeControl.getSurfacePosition().equals(lastSurfacePosition) + && mAnimation != null) { + startAnimation(mImeShowing, true /* forceRestart */); + } else if (!mImeShowing) { + removeImeSurface(); + } + }); + } + } + } + } + + @Override + public void showInsets(int types, boolean fromIme) { + if ((types & WindowInsets.Type.ime()) == 0) { + return; + } + if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); + mHandler.post(() -> startAnimation(true /* show */, false /* forceRestart */)); + } + + @Override + public void hideInsets(int types, boolean fromIme) { + if ((types & WindowInsets.Type.ime()) == 0) { + return; + } + if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); + mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */)); + } + + @Override + public void topFocusedWindowChanged(String packageName) { + // no-op + } + + /** + * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. + */ + private void setVisibleDirectly(boolean visible) { + mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); + try { + mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + } catch (RemoteException e) { + } + } + + private int imeTop(float surfaceOffset) { + return mImeFrame.top + (int) surfaceOffset; + } + + private boolean calcIsFloating(InsetsSource imeSource) { + final Rect frame = imeSource.getFrame(); + if (frame.height() == 0) { + return true; + } + // Some Floating Input Methods will still report a frame, but the frame is actually + // a nav-bar inset created by WM and not part of the IME (despite being reported as + // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar + // frame height so any reported frame that is <= nav-bar frame height is assumed to + // be floating. + return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId) + .navBarFrameHeight(); + } + + private void startAnimation(final boolean show, final boolean forceRestart) { + final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); + if (imeSource == null || mImeSourceControl == null) { + return; + } + final Rect newFrame = imeSource.getFrame(); + final boolean isFloating = calcIsFloating(imeSource) && show; + if (isFloating) { + // This is a "floating" or "expanded" IME, so to get animations, just + // pretend the ime has some size just below the screen. + mImeFrame.set(newFrame); + final int floatingInset = (int) (mDisplayController.getDisplayLayout(mDisplayId) + .density() * FLOATING_IME_BOTTOM_INSET); + mImeFrame.bottom -= floatingInset; + } else if (newFrame.height() != 0) { + // Don't set a new frame if it's empty and hiding -- this maintains continuity + mImeFrame.set(newFrame); + } + if (DEBUG) { + Slog.d(TAG, "Run startAnim show:" + show + " was:" + + (mAnimationDirection == DIRECTION_SHOW ? "SHOW" + : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE"))); + } + if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show) + || (mAnimationDirection == DIRECTION_HIDE && !show)) { + return; + } + boolean seek = false; + float seekValue = 0; + if (mAnimation != null) { + if (mAnimation.isRunning()) { + seekValue = (float) mAnimation.getAnimatedValue(); + seek = true; + } + mAnimation.cancel(); + } + final float defaultY = mImeSourceControl.getSurfacePosition().y; + final float x = mImeSourceControl.getSurfacePosition().x; + final float hiddenY = defaultY + mImeFrame.height(); + final float shownY = defaultY; + final float startY = show ? hiddenY : shownY; + final float endY = show ? shownY : hiddenY; + if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { + // IME is already showing, so set seek to end + seekValue = shownY; + seek = true; + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; + mImeShowing = show; + mAnimation = ValueAnimator.ofFloat(startY, endY); + mAnimation.setDuration( + show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); + if (seek) { + mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY)); + } + + mAnimation.addUpdateListener(animation -> { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + float value = (float) animation.getAnimatedValue(); + t.setPosition(mImeSourceControl.getLeash(), x, value); + final float alpha = (mAnimateAlpha || isFloating) + ? (value - hiddenY) / (shownY - hiddenY) : 1.f; + t.setAlpha(mImeSourceControl.getLeash(), alpha); + dispatchPositionChanged(mDisplayId, imeTop(value), t); + t.apply(); + mTransactionPool.release(t); + }); + mAnimation.setInterpolator(INTERPOLATOR); + mAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled = false; + + @Override + public void onAnimationStart(Animator animation) { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + t.setPosition(mImeSourceControl.getLeash(), x, startY); + if (DEBUG) { + Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" + + imeTop(hiddenY) + "->" + imeTop(shownY) + + " showing:" + (mAnimationDirection == DIRECTION_SHOW)); + } + int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY), + imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t); + mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0; + final float alpha = (mAnimateAlpha || isFloating) + ? (startY - hiddenY) / (shownY - hiddenY) + : 1.f; + t.setAlpha(mImeSourceControl.getLeash(), alpha); + if (mAnimationDirection == DIRECTION_SHOW) { + t.show(mImeSourceControl.getLeash()); + } + t.apply(); + mTransactionPool.release(t); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled); + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (!mCancelled) { + t.setPosition(mImeSourceControl.getLeash(), x, endY); + t.setAlpha(mImeSourceControl.getLeash(), 1.f); + } + dispatchEndPositioning(mDisplayId, mCancelled, t); + if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { + t.hide(mImeSourceControl.getLeash()); + removeImeSurface(); + } + t.apply(); + mTransactionPool.release(t); + + mAnimationDirection = DIRECTION_NONE; + mAnimation = null; + } + }); + if (!show) { + // When going away, queue up insets change first, otherwise any bounds changes + // can have a "flicker" of ime-provided insets. + setVisibleDirectly(false /* visible */); + } + mAnimation.start(); + if (show) { + // When showing away, queue up insets change last, otherwise any bounds changes + // can have a "flicker" of ime-provided insets. + setVisibleDirectly(true /* visible */); + } + } + } + + void removeImeSurface() { + final IInputMethodManager imms = getImms(); + if (imms != null) { + try { + // Remove the IME surface to make the insets invisible for + // non-client controlled insets. + imms.removeImeSurface(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to remove IME surface.", e); + } + } + } + + /** + * Allows other things to synchronize with the ime position + */ + public interface ImePositionProcessor { + /** + * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff + * behind the IME shouldn't be visible (for example during split-screen adjustment where + * there is nothing behind the ime). + */ + int IME_ANIMATION_NO_ALPHA = 1; + + /** @hide */ + @IntDef(prefix = {"IME_ANIMATION_"}, value = { + IME_ANIMATION_NO_ALPHA, + }) + @interface ImeAnimationFlags { + } + + /** + * Called when the IME position is starting to animate. + * + * @param hiddenTop The y position of the top of the IME surface when it is hidden. + * @param shownTop The y position of the top of the IME surface when it is shown. + * @param showing {@code true} when we are animating from hidden to shown, {@code false} + * when animating from shown to hidden. + * @param isFloating {@code true} when the ime is a floating ime (doesn't inset). + * @return flags that may alter how ime itself is animated (eg. no-alpha). + */ + @ImeAnimationFlags + default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean showing, boolean isFloating, SurfaceControl.Transaction t) { + return 0; + } + + /** + * Called when the ime position changed. This is expected to be a synchronous call on the + * animation thread. Operations can be added to the transaction to be applied in sync. + * + * @param imeTop The current y position of the top of the IME surface. + */ + default void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { + } + + /** + * Called when the IME position is done animating. + * + * @param cancel {@code true} if this was cancelled. This implies another start is coming. + */ + default void onImeEndPositioning(int displayId, boolean cancel, + SurfaceControl.Transaction t) { + } + } + + public IInputMethodManager getImms() { + return IInputMethodManager.Stub.asInterface( + ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java new file mode 100644 index 000000000000..3181dbf74ace --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -0,0 +1,511 @@ +/* + * 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.wm.shell.common; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +import static android.content.res.Configuration.UI_MODE_TYPE_MASK; +import static android.os.Process.SYSTEM_UID; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; +import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.SystemProperties; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.RotationUtils; +import android.util.Size; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.Surface; + +import com.android.internal.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains information about the layout-properties of a display. This refers to internal layout + * like insets/cutout/rotation. In general, this can be thought of as the shell analog to + * DisplayPolicy. + */ +public class DisplayLayout { + @IntDef(prefix = { "NAV_BAR_" }, value = { + NAV_BAR_LEFT, + NAV_BAR_RIGHT, + NAV_BAR_BOTTOM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavBarPosition {} + + // Navigation bar position values + public static final int NAV_BAR_LEFT = 1 << 0; + public static final int NAV_BAR_RIGHT = 1 << 1; + public static final int NAV_BAR_BOTTOM = 1 << 2; + + private int mUiMode; + private int mWidth; + private int mHeight; + private DisplayCutout mCutout; + private int mRotation; + private int mDensityDpi; + private final Rect mNonDecorInsets = new Rect(); + private final Rect mStableInsets = new Rect(); + private boolean mHasNavigationBar = false; + private boolean mHasStatusBar = false; + private int mNavBarFrameHeight = 0; + + /** + * Create empty layout. + */ + public DisplayLayout() { + } + + /** + * Construct a custom display layout using a DisplayInfo. + * @param info + * @param res + */ + public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar, + boolean hasStatusBar) { + init(info, res, hasNavigationBar, hasStatusBar); + } + + /** + * Construct a display layout based on a live display. + * @param context Used for resources. + */ + public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) { + final int displayId = rawDisplay.getDisplayId(); + DisplayInfo info = new DisplayInfo(); + rawDisplay.getDisplayInfo(info); + init(info, context.getResources(), hasNavigationBar(info, context, displayId), + hasStatusBar(displayId)); + } + + public DisplayLayout(DisplayLayout dl) { + set(dl); + } + + /** sets this DisplayLayout to a copy of another on. */ + public void set(DisplayLayout dl) { + mUiMode = dl.mUiMode; + mWidth = dl.mWidth; + mHeight = dl.mHeight; + mCutout = dl.mCutout; + mRotation = dl.mRotation; + mDensityDpi = dl.mDensityDpi; + mHasNavigationBar = dl.mHasNavigationBar; + mHasStatusBar = dl.mHasStatusBar; + mNonDecorInsets.set(dl.mNonDecorInsets); + mStableInsets.set(dl.mStableInsets); + } + + private void init(DisplayInfo info, Resources res, boolean hasNavigationBar, + boolean hasStatusBar) { + mUiMode = res.getConfiguration().uiMode; + mWidth = info.logicalWidth; + mHeight = info.logicalHeight; + mRotation = info.rotation; + mCutout = info.displayCutout; + mDensityDpi = info.logicalDensityDpi; + mHasNavigationBar = hasNavigationBar; + mHasStatusBar = hasStatusBar; + recalcInsets(res); + } + + private void recalcInsets(Resources res) { + computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets, + mHasNavigationBar); + mStableInsets.set(mNonDecorInsets); + if (mHasStatusBar) { + convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar); + } + mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight); + } + + /** + * Apply a rotation to this layout and its parameters. + * @param res + * @param targetRotation + */ + public void rotateTo(Resources res, @Surface.Rotation int targetRotation) { + final int rotationDelta = (targetRotation - mRotation + 4) % 4; + final boolean changeOrient = (rotationDelta % 2) != 0; + + final int origWidth = mWidth; + final int origHeight = mHeight; + + mRotation = targetRotation; + if (changeOrient) { + mWidth = origHeight; + mHeight = origWidth; + } + + if (mCutout != null && !mCutout.isEmpty()) { + mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth, + origHeight); + } + + recalcInsets(res); + } + + /** Get this layout's non-decor insets. */ + public Rect nonDecorInsets() { + return mNonDecorInsets; + } + + /** Get this layout's stable insets. */ + public Rect stableInsets() { + return mStableInsets; + } + + /** Get this layout's width. */ + public int width() { + return mWidth; + } + + /** Get this layout's height. */ + public int height() { + return mHeight; + } + + /** Get this layout's display rotation. */ + public int rotation() { + return mRotation; + } + + /** Get this layout's display density. */ + public int densityDpi() { + return mDensityDpi; + } + + /** Get the density scale for the display. */ + public float density() { + return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + } + + /** Get whether this layout is landscape. */ + public boolean isLandscape() { + return mWidth > mHeight; + } + + /** Get the navbar frame height (used by ime). */ + public int navBarFrameHeight() { + return mNavBarFrameHeight; + } + + /** Gets the orientation of this layout */ + public int getOrientation() { + return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + } + + /** Gets the calculated stable-bounds for this layout */ + public void getStableBounds(Rect outBounds) { + outBounds.set(0, 0, mWidth, mHeight); + outBounds.inset(mStableInsets); + } + + /** + * Gets navigation bar position for this layout + * @return Navigation bar position for this layout. + */ + public @NavBarPosition int getNavigationBarPosition(Resources res) { + return navigationBarPosition(res, mWidth, mHeight, mRotation); + } + + /** + * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` + * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and + * remains at 0,0 after rotation. + * + * Only 'bounds' is mutated. + */ + public static void rotateBounds(Rect inOutBounds, Rect parentBounds, int delta) { + int rdelta = ((delta % 4) + 4) % 4; + int origLeft = inOutBounds.left; + switch (rdelta) { + case 0: + return; + case 1: + inOutBounds.left = inOutBounds.top; + inOutBounds.top = parentBounds.right - inOutBounds.right; + inOutBounds.right = inOutBounds.bottom; + inOutBounds.bottom = parentBounds.right - origLeft; + return; + case 2: + inOutBounds.left = parentBounds.right - inOutBounds.right; + inOutBounds.right = parentBounds.right - origLeft; + return; + case 3: + inOutBounds.left = parentBounds.bottom - inOutBounds.bottom; + inOutBounds.bottom = inOutBounds.right; + inOutBounds.right = parentBounds.bottom - inOutBounds.top; + inOutBounds.top = origLeft; + return; + } + } + + /** + * Calculates the stable insets if we already have the non-decor insets. + */ + private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets, + int displayWidth, int displayHeight, boolean hasStatusBar) { + if (!hasStatusBar) { + return; + } + int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res); + inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight); + } + + /** + * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system + * bar or button bar. + * + * @param displayRotation the current display rotation + * @param displayWidth the current display width + * @param displayHeight the current display height + * @param displayCutout the current display cutout + * @param outInsets the insets to return + */ + static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth, + int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, + boolean hasNavigationBar) { + outInsets.setEmpty(); + + // Only navigation bar + if (hasNavigationBar) { + int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation); + int navBarSize = + getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode); + if (position == NAV_BAR_BOTTOM) { + outInsets.bottom = navBarSize; + } else if (position == NAV_BAR_RIGHT) { + outInsets.right = navBarSize; + } else if (position == NAV_BAR_LEFT) { + outInsets.left = navBarSize; + } + } + + if (displayCutout != null) { + outInsets.left += displayCutout.getSafeInsetLeft(); + outInsets.top += displayCutout.getSafeInsetTop(); + outInsets.right += displayCutout.getSafeInsetRight(); + outInsets.bottom += displayCutout.getSafeInsetBottom(); + } + } + + /** + * Calculates the stable insets without running a layout. + * + * @param displayRotation the current display rotation + * @param displayWidth the current display width + * @param displayHeight the current display height + * @param displayCutout the current display cutout + * @param outInsets the insets to return + */ + static void computeStableInsets(Resources res, int displayRotation, int displayWidth, + int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, + boolean hasNavigationBar, boolean hasStatusBar) { + outInsets.setEmpty(); + + // Navigation bar and status bar. + computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout, + uiMode, outInsets, hasNavigationBar); + convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight, + hasStatusBar); + } + + /** Retrieve the statusbar height from resources. */ + static int getStatusBarHeight(boolean landscape, Resources res) { + return landscape ? res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_landscape) + : res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_portrait); + } + + /** Calculate the DisplayCutout for a particular display size/rotation. */ + public static DisplayCutout calculateDisplayCutoutForRotation( + DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) { + if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { + return null; + } + final Insets waterfallInsets = + RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); + if (rotation == ROTATION_0) { + return computeSafeInsets(cutout, displayWidth, displayHeight); + } + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + Rect[] cutoutRects = cutout.getBoundingRectsAll(); + final Rect[] newBounds = new Rect[cutoutRects.length]; + final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight); + for (int i = 0; i < cutoutRects.length; ++i) { + final Rect rect = new Rect(cutoutRects[i]); + if (!rect.isEmpty()) { + rotateBounds(rect, displayBounds, rotation); + } + newBounds[getBoundIndexFromRotation(i, rotation)] = rect; + } + return computeSafeInsets( + DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets), + rotated ? displayHeight : displayWidth, + rotated ? displayWidth : displayHeight); + } + + private static int getBoundIndexFromRotation(int index, int rotation) { + return (index - rotation) < 0 + ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH + : index - rotation; + } + + /** Calculate safe insets. */ + public static DisplayCutout computeSafeInsets(DisplayCutout inner, + int displayWidth, int displayHeight) { + if (inner == DisplayCutout.NO_CUTOUT) { + return null; + } + + final Size displaySize = new Size(displayWidth, displayHeight); + final Rect safeInsets = computeSafeInsets(displaySize, inner); + return inner.replaceSafeInsets(safeInsets); + } + + private static Rect computeSafeInsets( + Size displaySize, DisplayCutout cutout) { + if (displaySize.getWidth() == displaySize.getHeight()) { + throw new UnsupportedOperationException("not implemented: display=" + displaySize + + " cutout=" + cutout); + } + + int leftInset = Math.max(cutout.getWaterfallInsets().left, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT)); + int topInset = Math.max(cutout.getWaterfallInsets().top, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP)); + int rightInset = Math.max(cutout.getWaterfallInsets().right, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT)); + int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(), + Gravity.BOTTOM)); + + return new Rect(leftInset, topInset, rightInset, bottomInset); + } + + private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) { + if (boundingRect.isEmpty()) { + return 0; + } + + int inset = 0; + switch (gravity) { + case Gravity.TOP: + return Math.max(inset, boundingRect.bottom); + case Gravity.BOTTOM: + return Math.max(inset, display.getHeight() - boundingRect.top); + case Gravity.LEFT: + return Math.max(inset, boundingRect.right); + case Gravity.RIGHT: + return Math.max(inset, display.getWidth() - boundingRect.left); + default: + throw new IllegalArgumentException("unknown gravity: " + gravity); + } + } + + static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + // Allow a system property to override this. Used by the emulator. + final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + return false; + } else if ("0".equals(navBarOverride)) { + return true; + } + return context.getResources().getBoolean(R.bool.config_showNavigationBar); + } else { + boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL + && info.ownerUid != SYSTEM_UID; + final ContentResolver resolver = context.getContentResolver(); + boolean forceDesktopOnExternal = Settings.Global.getInt(resolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; + + return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0 + || (forceDesktopOnExternal && !isUntrustedVirtualDisplay)); + // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow. + } + } + + static boolean hasStatusBar(int displayId) { + return displayId == Display.DEFAULT_DISPLAY; + } + + /** Retrieve navigation bar position from resources based on rotation and size. */ + public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth, + int displayHeight, int rotation) { + boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean( + com.android.internal.R.bool.config_navBarCanMove); + if (navBarCanMove && displayWidth > displayHeight) { + if (rotation == Surface.ROTATION_90) { + return NAV_BAR_RIGHT; + } else { + return NAV_BAR_LEFT; + } + } + return NAV_BAR_BOTTOM; + } + + /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */ + public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape, + int uiMode) { + final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR; + if (carMode) { + if (navBarSide == NAV_BAR_BOTTOM) { + return res.getDimensionPixelSize(landscape + ? R.dimen.navigation_bar_height_landscape_car_mode + : R.dimen.navigation_bar_height_car_mode); + } else { + return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode); + } + + } else { + if (navBarSide == NAV_BAR_BOTTOM) { + return res.getDimensionPixelSize(landscape + ? R.dimen.navigation_bar_height_landscape + : R.dimen.navigation_bar_height); + } else { + return res.getDimensionPixelSize(R.dimen.navigation_bar_width); + } + } + } + + /** @see com.android.server.wm.DisplayPolicy#getNavigationBarFrameHeight */ + public static int getNavigationBarFrameHeight(Resources res, boolean landscape) { + return res.getDimensionPixelSize(landscape + ? R.dimen.navigation_bar_frame_height_landscape + : R.dimen.navigation_bar_frame_height); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java new file mode 100644 index 000000000000..8abe9eeb6a9a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -0,0 +1,393 @@ +/* + * 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.wm.shell.common; + +import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.DragEvent; +import android.view.IScrollCaptureController; +import android.view.IWindow; +import android.view.IWindowManager; +import android.view.IWindowSession; +import android.view.IWindowSessionCallback; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import com.android.internal.os.IResultReceiver; + +import java.util.HashMap; + +/** + * Represents the "windowing" layer of the WM Shell. This layer allows shell components to place and + * manipulate windows without talking to WindowManager. + */ +public class SystemWindows { + private static final String TAG = "SystemWindows"; + + private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>(); + private final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); + private final DisplayController mDisplayController; + private final IWindowManager mWmService; + private IWindowSession mSession; + + private final DisplayController.OnDisplaysChangedListener mDisplayListener = + new DisplayController.OnDisplaysChangedListener() { + @Override + public void onDisplayAdded(int displayId) { } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + PerDisplay pd = mPerDisplay.get(displayId); + if (pd == null) { + return; + } + pd.updateConfiguration(newConfig); + } + + @Override + public void onDisplayRemoved(int displayId) { } + }; + + public SystemWindows(DisplayController displayController, IWindowManager wmService) { + mWmService = wmService; + mDisplayController = displayController; + mDisplayController.addDisplayWindowListener(mDisplayListener); + try { + mSession = wmService.openSession( + new IWindowSessionCallback.Stub() { + @Override + public void onAnimatorScaleChanged(float scale) {} + }); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to create layer", e); + } + } + + /** + * Adds a view to system-ui window management. + */ + public void addView(View view, WindowManager.LayoutParams attrs, int displayId, + int windowType) { + PerDisplay pd = mPerDisplay.get(displayId); + if (pd == null) { + pd = new PerDisplay(displayId); + mPerDisplay.put(displayId, pd); + } + pd.addView(view, attrs, windowType); + } + + /** + * Removes a view from system-ui window management. + * @param view + */ + public void removeView(View view) { + SurfaceControlViewHost root = mViewRoots.remove(view); + root.release(); + } + + /** + * Updates the layout params of a view. + */ + public void updateViewLayout(@NonNull View view, ViewGroup.LayoutParams params) { + SurfaceControlViewHost root = mViewRoots.get(view); + if (root == null || !(params instanceof WindowManager.LayoutParams)) { + return; + } + view.setLayoutParams(params); + root.relayout((WindowManager.LayoutParams) params); + } + + /** + * Sets the touchable region of a view's window. This will be cropped to the window size. + * @param view + * @param region + */ + public void setTouchableRegion(@NonNull View view, Region region) { + SurfaceControlViewHost root = mViewRoots.get(view); + if (root == null) { + return; + } + WindowlessWindowManager wwm = root.getWindowlessWM(); + if (!(wwm instanceof SysUiWindowManager)) { + return; + } + ((SysUiWindowManager) wwm).setTouchableRegionForWindow(view, region); + } + + /** + * Adds a root for system-ui window management with no views. Only useful for IME. + */ + public void addRoot(int displayId, int windowType) { + PerDisplay pd = mPerDisplay.get(displayId); + if (pd == null) { + pd = new PerDisplay(displayId); + mPerDisplay.put(displayId, pd); + } + pd.addRoot(windowType); + } + + /** + * Get the IWindow token for a specific root. + * + * @param windowType A window type from {@link WindowManager}. + */ + IWindow getWindow(int displayId, int windowType) { + PerDisplay pd = mPerDisplay.get(displayId); + if (pd == null) { + return null; + } + return pd.getWindow(windowType); + } + + /** + * Gets the SurfaceControl associated with a root view. This is the same surface that backs the + * ViewRootImpl. + */ + public SurfaceControl getViewSurface(View rootView) { + for (int i = 0; i < mPerDisplay.size(); ++i) { + for (int iWm = 0; iWm < mPerDisplay.valueAt(i).mWwms.size(); ++iWm) { + SurfaceControl out = mPerDisplay.valueAt(i).mWwms.valueAt(iWm) + .getSurfaceControlForWindow(rootView); + if (out != null) { + return out; + } + } + } + return null; + } + + private class PerDisplay { + final int mDisplayId; + private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>(); + + PerDisplay(int displayId) { + mDisplayId = displayId; + } + + public void addView(View view, WindowManager.LayoutParams attrs, int windowType) { + SysUiWindowManager wwm = addRoot(windowType); + if (wwm == null) { + Slog.e(TAG, "Unable to create systemui root"); + return; + } + final Display display = mDisplayController.getDisplay(mDisplayId); + SurfaceControlViewHost viewRoot = + new SurfaceControlViewHost( + view.getContext(), display, wwm, true /* useSfChoreographer */); + attrs.flags |= FLAG_HARDWARE_ACCELERATED; + viewRoot.setView(view, attrs); + mViewRoots.put(view, viewRoot); + + try { + mWmService.setShellRootAccessibilityWindow(mDisplayId, windowType, + viewRoot.getWindowToken()); + } catch (RemoteException e) { + Slog.e(TAG, "Error setting accessibility window for " + mDisplayId + ":" + + windowType, e); + } + } + + SysUiWindowManager addRoot(int windowType) { + SysUiWindowManager wwm = mWwms.get(windowType); + if (wwm != null) { + return wwm; + } + SurfaceControl rootSurface = null; + ContainerWindow win = new ContainerWindow(); + try { + rootSurface = mWmService.addShellRoot(mDisplayId, win, windowType); + } catch (RemoteException e) { + } + if (rootSurface == null) { + Slog.e(TAG, "Unable to get root surfacecontrol for systemui"); + return null; + } + Context displayContext = mDisplayController.getDisplayContext(mDisplayId); + wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win); + mWwms.put(windowType, wwm); + return wwm; + } + + IWindow getWindow(int windowType) { + SysUiWindowManager wwm = mWwms.get(windowType); + if (wwm == null) { + return null; + } + return wwm.mContainerWindow; + } + + void updateConfiguration(Configuration configuration) { + for (int i = 0; i < mWwms.size(); ++i) { + mWwms.valueAt(i).updateConfiguration(configuration); + } + } + } + + /** + * A subclass of WindowlessWindowManager that provides insets to its viewroots. + */ + public class SysUiWindowManager extends WindowlessWindowManager { + final int mDisplayId; + ContainerWindow mContainerWindow; + public SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface, + ContainerWindow container) { + super(ctx.getResources().getConfiguration(), rootSurface, null /* hostInputToken */); + mContainerWindow = container; + mDisplayId = displayId; + } + + @Override + public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, + int requestedWidth, int requestedHeight, int viewVisibility, int flags, + long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, + Rect outVisibleInsets, Rect outStableInsets, + DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { + int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, + viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, + outContentInsets, outVisibleInsets, outStableInsets, + cutout, mergedConfiguration, outSurfaceControl, outInsetsState, + outActiveControls, outSurfaceSize, outBLASTSurfaceControl); + if (res != 0) { + return res; + } + DisplayLayout dl = mDisplayController.getDisplayLayout(mDisplayId); + outStableInsets.set(dl.stableInsets()); + return 0; + } + + void updateConfiguration(Configuration configuration) { + setConfiguration(configuration); + } + + SurfaceControl getSurfaceControlForWindow(View rootView) { + return getSurfaceControl(rootView); + } + + void setTouchableRegionForWindow(View rootView, Region region) { + IBinder token = rootView.getWindowToken(); + if (token == null) { + return; + } + setTouchRegion(token, region); + } + } + + static class ContainerWindow extends IWindow.Stub { + ContainerWindow() {} + + @Override + public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, Rect stableInsets, + boolean reportDraw, MergedConfiguration newMergedConfiguration, Rect backDropFrame, + boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, + DisplayCutout.ParcelableWrapper displayCutout) {} + + @Override + public void locationInParentDisplayChanged(Point offset) {} + + @Override + public void insetsChanged(InsetsState insetsState) {} + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) {} + + @Override + public void showInsets(int types, boolean fromIme) {} + + @Override + public void hideInsets(int types, boolean fromIme) {} + + @Override + public void moved(int newX, int newY) {} + + @Override + public void dispatchAppVisibility(boolean visible) {} + + @Override + public void dispatchGetNewSurface() {} + + @Override + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {} + + @Override + public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {} + + @Override + public void closeSystemDialogs(String reason) {} + + @Override + public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, + float zoom, boolean sync) {} + + @Override + public void dispatchWallpaperCommand(String action, int x, int y, + int z, Bundle extras, boolean sync) {} + + /* Drag/drop */ + @Override + public void dispatchDragEvent(DragEvent event) {} + + @Override + public void updatePointerIcon(float x, float y) {} + + @Override + public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, + int localValue, int localChanges) {} + + @Override + public void dispatchWindowShown() {} + + @Override + public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {} + + @Override + public void dispatchPointerCaptureChanged(boolean hasCapture) {} + + @Override + public void requestScrollCapture(IScrollCaptureController controller) { + try { + controller.onClientUnavailable(); + } catch (RemoteException ex) { + // ignore + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java new file mode 100644 index 000000000000..4c34566b0d98 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 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.wm.shell.common; + +import android.util.Pools; +import android.view.SurfaceControl; + +/** + * Provides a synchronized pool of {@link SurfaceControl.Transaction}s to minimize allocations. + */ +public class TransactionPool { + private final Pools.SynchronizedPool<SurfaceControl.Transaction> mTransactionPool = + new Pools.SynchronizedPool<>(4); + + public TransactionPool() { + } + + /** Gets a transaction from the pool. */ + public SurfaceControl.Transaction acquire() { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (t == null) { + return new SurfaceControl.Transaction(); + } + return t; + } + + /** + * Return a transaction to the pool. DO NOT call {@link SurfaceControl.Transaction#close()} if + * returning to pool. + */ + public void release(SurfaceControl.Transaction t) { + if (!mTransactionPool.release(t)) { + t.close(); + } + } +} diff --git a/libs/WindowManager/Shell/tests/Android.bp b/libs/WindowManager/Shell/tests/Android.bp index 78fa45ebdf94..9868879cebb9 100644 --- a/libs/WindowManager/Shell/tests/Android.bp +++ b/libs/WindowManager/Shell/tests/Android.bp @@ -36,9 +36,6 @@ android_test { "libstaticjvmtiagent", ], - sdk_version: "current", - platform_apis: true, - optimize: { enabled: false, }, diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java index 376875b143a1..f1ead3c8a441 100644 --- a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java +++ b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,13 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.tests; +package com.android.wm.shell; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.WindowManagerShell; - import org.junit.Test; import org.junit.runner.RunWith; diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java new file mode 100644 index 000000000000..2b5b77e49e3a --- /dev/null +++ b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java @@ -0,0 +1,140 @@ +/* + * 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.wm.shell.common; + +import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.Rect; +import android.view.DisplayCutout; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import com.android.internal.R; + +import org.junit.Test; + +@SmallTest +public class DisplayLayoutTest { + + @Test + public void testInsets() { + Resources res = createResources(40, 50, false, 30, 40); + // Test empty display, no bars or anything + DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0); + DisplayLayout dl = new DisplayLayout(info, res, false, false); + assertEquals(new Rect(0, 0, 0, 0), dl.stableInsets()); + assertEquals(new Rect(0, 0, 0, 0), dl.nonDecorInsets()); + + // Test with bars + dl = new DisplayLayout(info, res, true, true); + assertEquals(new Rect(0, 40, 0, 50), dl.stableInsets()); + assertEquals(new Rect(0, 0, 0, 50), dl.nonDecorInsets()); + + // Test just cutout + info = createDisplayInfo(1000, 1500, 60, ROTATION_0); + dl = new DisplayLayout(info, res, false, false); + assertEquals(new Rect(0, 60, 0, 0), dl.stableInsets()); + assertEquals(new Rect(0, 60, 0, 0), dl.nonDecorInsets()); + + // Test with bars and cutout + dl = new DisplayLayout(info, res, true, true); + assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets()); + assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets()); + } + + @Test + public void testRotate() { + // Basic rotate utility + Rect testParent = new Rect(0, 0, 1000, 600); + Rect testInner = new Rect(40, 20, 120, 80); + Rect testResult = new Rect(testInner); + DisplayLayout.rotateBounds(testResult, testParent, 1); + assertEquals(new Rect(20, 880, 80, 960), testResult); + testResult.set(testInner); + DisplayLayout.rotateBounds(testResult, testParent, 2); + assertEquals(new Rect(880, 20, 960, 80), testResult); + testResult.set(testInner); + DisplayLayout.rotateBounds(testResult, testParent, 3); + assertEquals(new Rect(520, 40, 580, 120), testResult); + + Resources res = createResources(40, 50, false, 30, 40); + DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0); + DisplayLayout dl = new DisplayLayout(info, res, true, true); + assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets()); + assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets()); + + // Rotate to 90 + dl.rotateTo(res, ROTATION_90); + assertEquals(new Rect(60, 30, 0, 40), dl.stableInsets()); + assertEquals(new Rect(60, 0, 0, 40), dl.nonDecorInsets()); + + // Rotate with moving navbar + res = createResources(40, 50, true, 30, 40); + dl = new DisplayLayout(info, res, true, true); + dl.rotateTo(res, ROTATION_270); + assertEquals(new Rect(40, 30, 60, 0), dl.stableInsets()); + assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets()); + } + + private Resources createResources( + int navLand, int navPort, boolean navMoves, int statusLand, int statusPort) { + Configuration cfg = new Configuration(); + cfg.uiMode = UI_MODE_TYPE_NORMAL; + Resources res = mock(Resources.class); + doReturn(navLand).when(res).getDimensionPixelSize( + R.dimen.navigation_bar_height_landscape_car_mode); + doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_car_mode); + doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode); + doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_landscape); + doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height); + doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width); + doReturn(navMoves).when(res).getBoolean(R.bool.config_navBarCanMove); + doReturn(statusLand).when(res).getDimensionPixelSize(R.dimen.status_bar_height_landscape); + doReturn(statusPort).when(res).getDimensionPixelSize(R.dimen.status_bar_height_portrait); + doReturn(cfg).when(res).getConfiguration(); + return res; + } + + private DisplayInfo createDisplayInfo(int width, int height, int cutoutHeight, int rotation) { + DisplayInfo info = new DisplayInfo(); + info.logicalWidth = width; + info.logicalHeight = height; + info.rotation = rotation; + if (cutoutHeight > 0) { + info.displayCutout = new DisplayCutout( + Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, null /* boundLeft */, + new Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight, + cutoutHeight) /* boundTop */, null /* boundRight */, + null /* boundBottom */); + } else { + info.displayCutout = DisplayCutout.NO_CUTOUT; + } + info.logicalDensityDpi = 300; + return info; + } +} diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp new file mode 100644 index 000000000000..77ef8dfb9725 --- /dev/null +++ b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp @@ -0,0 +1,46 @@ +// Copyright (C) 2020 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. + +cc_fuzz { + name: "resourcefile_fuzzer", + srcs: [ + "resourcefile_fuzzer.cpp", + ], + host_supported: true, + corpus: ["corpus/*"], + static_libs: ["libgmock"], + target: { + android: { + shared_libs:[ + "libandroidfw", + "libbase", + "libcutils", + "libutils", + "libziparchive", + "libui", + ], + }, + host: { + static_libs: [ + "libandroidfw", + "libbase", + "libcutils", + "libutils", + "libziparchive", + "liblog", + "libz", + ], + }, + }, +} diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/corpus/resources.arsc b/libs/androidfw/fuzz/resourcefile_fuzzer/corpus/resources.arsc Binary files differnew file mode 100644 index 000000000000..3cf2ea733d28 --- /dev/null +++ b/libs/androidfw/fuzz/resourcefile_fuzzer/corpus/resources.arsc diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/resourcefile_fuzzer.cpp b/libs/androidfw/fuzz/resourcefile_fuzzer/resourcefile_fuzzer.cpp new file mode 100644 index 000000000000..96d44ab8e45c --- /dev/null +++ b/libs/androidfw/fuzz/resourcefile_fuzzer/resourcefile_fuzzer.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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. + */ + +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <string> +#include <memory> + +#include <androidfw/ApkAssets.h> +#include <androidfw/LoadedArsc.h> +#include <androidfw/StringPiece.h> + +#include <fuzzer/FuzzedDataProvider.h> + +using android::ApkAssets; +using android::LoadedArsc; +using android::StringPiece; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + + std::unique_ptr<const LoadedArsc> loaded_arsc = + LoadedArsc::Load(StringPiece(reinterpret_cast<const char*>(data), size)); + + return 0; +}
\ No newline at end of file diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index c0a24438987a..1a89cfd5d0ad 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -209,11 +209,8 @@ static SkImageInfo validateAlpha(const SkImageInfo& info) { void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes) { mInfo = validateAlpha(newInfo); - // Dirty hack is dirty - // TODO: Figure something out here, Skia's current design makes this - // really hard to work with. Skia really, really wants immutable objects, - // but with the nested-ref-count hackery going on that's just not - // feasible without going insane trying to figure it out + // TODO: Skia intends for SkPixelRef to be immutable, but this method + // modifies it. Find another way to support reusing the same pixel memory. this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes); } |