summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java273
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java493
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java511
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java393
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java50
-rw-r--r--libs/WindowManager/Shell/tests/Android.bp3
-rw-r--r--libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java (renamed from libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java)6
-rw-r--r--libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java140
-rw-r--r--libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp46
-rw-r--r--libs/androidfw/fuzz/resourcefile_fuzzer/corpus/resources.arscbin0 -> 724 bytes
-rw-r--r--libs/androidfw/fuzz/resourcefile_fuzzer/resourcefile_fuzzer.cpp39
-rw-r--r--libs/hwui/hwui/Bitmap.cpp7
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
new file mode 100644
index 000000000000..3cf2ea733d28
--- /dev/null
+++ b/libs/androidfw/fuzz/resourcefile_fuzzer/corpus/resources.arsc
Binary files differ
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);
}