diff options
| author | Jorim Jaggi <jjaggi@google.com> | 2018-10-23 18:31:52 +0200 |
|---|---|---|
| committer | Jorim Jaggi <jjaggi@google.com> | 2018-12-03 21:53:09 +0100 |
| commit | b603095494b0240797973f443f38875260897a28 (patch) | |
| tree | b69189872cfe51a1282908658f2ad4075b53d634 /core/java/android | |
| parent | 4bcd414ea9b8ac772bcb95e30bebaba819a2e409 (diff) | |
A brave new world for window insets (2 and 3/n)
Implements basic API's to control windows generating insets in
the new insets world.
Test: CTS tests will be added at some point in the future
Bug: 118118435
Change-Id: I722d2e58c68734ac131b12da3d9978e946292130
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/view/IWindow.aidl | 6 | ||||
| -rw-r--r-- | core/java/android/view/InsetsController.java | 70 | ||||
| -rw-r--r-- | core/java/android/view/InsetsSourceConsumer.java | 95 | ||||
| -rw-r--r-- | core/java/android/view/InsetsSourceControl.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/view/InsetsSourceControl.java | 72 | ||||
| -rw-r--r-- | core/java/android/view/InsetsState.java | 19 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 14 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 42 | ||||
| -rw-r--r-- | core/java/android/view/Window.java | 7 | ||||
| -rw-r--r-- | core/java/android/view/WindowInsets.java | 69 | ||||
| -rw-r--r-- | core/java/android/view/WindowInsetsController.java | 54 |
11 files changed, 464 insertions, 3 deletions
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index af41b6942a5e..5e6d3d19fa2f 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -25,6 +25,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.DisplayCutout; import android.view.InsetsState; +import android.view.InsetsSourceControl; import com.android.internal.os.IResultReceiver; import android.util.MergedConfiguration; @@ -60,6 +61,11 @@ oneway interface IWindow { */ void insetsChanged(in InsetsState insetsState); + /** + * Called when this window retrieved control over a specified set of inset sources. + */ + void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls); + void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7841d0417a2b..ba5340c826d3 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,17 +16,30 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; +import android.util.ArraySet; +import android.util.SparseArray; +import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets.Type.InsetType; +import android.view.InsetsState.InternalInsetType; + +import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; /** * Implements {@link WindowInsetsController} on the client. + * @hide */ -class InsetsController { +public class InsetsController implements WindowInsetsController { private final InsetsState mState = new InsetsState(); private final Rect mFrame = new Rect(); + private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); + + private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); void onFrameChanged(Rect frame) { mFrame.set(frame); @@ -48,6 +61,61 @@ class InsetsController { return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout); } + /** + * Called when the server has dispatched us a new set of inset controls. + */ + public void onControlsChanged(InsetsSourceControl[] activeControls) { + if (activeControls != null) { + for (InsetsSourceControl activeControl : activeControls) { + mTmpControlArray.put(activeControl.getType(), activeControl); + } + } + + // Ensure to update all existing source consumers + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + final InsetsSourceControl control = mTmpControlArray.get(consumer.getType()); + + // control may be null, but we still need to update the control to null if it got + // revoked. + consumer.setControl(control); + } + + // Ensure to create source consumers if not available yet. + for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { + final InsetsSourceControl control = mTmpControlArray.valueAt(i); + getSourceConsumer(control.getType()).setControl(control); + } + mTmpControlArray.clear(); + } + + @Override + public void show(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).show(); + } + } + + @Override + public void hide(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).hide(); + } + } + + @VisibleForTesting + public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) { + InsetsSourceConsumer controller = mSourceConsumers.get(type); + if (controller != null) { + return controller; + } + controller = new InsetsSourceConsumer(type, mState, Transaction::new); + mSourceConsumers.put(type, controller); + return controller; + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java new file mode 100644 index 000000000000..e74aa8dfcf4e --- /dev/null +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 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 android.view; + +import android.annotation.Nullable; +import android.view.SurfaceControl.Transaction; +import android.view.InsetsState.InternalInsetType; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.function.Supplier; + +/** + * Controls the visibility and animations of a single window insets source. + * @hide + */ +public class InsetsSourceConsumer { + + private final Supplier<Transaction> mTransactionSupplier; + private final @InternalInsetType int mType; + private final InsetsState mState; + private @Nullable InsetsSourceControl mControl; + private boolean mHidden; + + public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, + Supplier<Transaction> transactionSupplier) { + mType = type; + mState = state; + mTransactionSupplier = transactionSupplier; + } + + public void setControl(@Nullable InsetsSourceControl control) { + if (mControl == control) { + return; + } + mControl = control; + applyHiddenToControl(); + } + + @VisibleForTesting + public InsetsSourceControl getControl() { + return mControl; + } + + int getType() { + return mType; + } + + @VisibleForTesting + public void show() { + setHidden(false); + } + + @VisibleForTesting + public void hide() { + setHidden(true); + } + + private void setHidden(boolean hidden) { + if (mHidden == hidden) { + return; + } + mHidden = hidden; + applyHiddenToControl(); + } + + private void applyHiddenToControl() { + if (mControl == null) { + return; + } + + // TODO: Animation + final Transaction t = mTransactionSupplier.get(); + if (mHidden) { + t.hide(mControl.getLeash()); + } else { + t.show(mControl.getLeash()); + } + t.apply(); + } +} diff --git a/core/java/android/view/InsetsSourceControl.aidl b/core/java/android/view/InsetsSourceControl.aidl new file mode 100644 index 000000000000..755bf456658d --- /dev/null +++ b/core/java/android/view/InsetsSourceControl.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, 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 android.view; + +parcelable InsetsSourceControl; diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java new file mode 100644 index 000000000000..9383e6c57854 --- /dev/null +++ b/core/java/android/view/InsetsSourceControl.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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 android.view; + +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.InsetsState.InternalInsetType; + +/** + * Represents a parcelable object to allow controlling a single {@link InsetsSource}. + * @hide + */ +public class InsetsSourceControl implements Parcelable { + + private final @InternalInsetType int mType; + private final SurfaceControl mLeash; + + public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash) { + mType = type; + mLeash = leash; + } + + public int getType() { + return mType; + } + + public SurfaceControl getLeash() { + return mLeash; + } + + public InsetsSourceControl(Parcel in) { + mType = in.readInt(); + mLeash = in.readParcelable(null /* loader */); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeParcelable(mLeash, 0 /* flags*/); + } + + public static final Creator<InsetsSourceControl> CREATOR + = new Creator<InsetsSourceControl>() { + public InsetsSourceControl createFromParcel(Parcel in) { + return new InsetsSourceControl(in); + } + + public InsetsSourceControl[] newArray(int size) { + return new InsetsSourceControl[size]; + } + }; +} diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 9895adcad23a..689b14fe29c6 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -22,6 +22,9 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.ArraySet; +import android.view.WindowInsets.Type; +import android.view.WindowInsets.Type.InsetType; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -148,6 +151,22 @@ public class InsetsState implements Parcelable { } } + public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) { + final ArraySet<Integer> result = new ArraySet<>(); + if ((insetTypes & Type.TOP_BAR) != 0) { + result.add(TYPE_TOP_BAR); + } + if ((insetTypes & Type.SIDE_BARS) != 0) { + result.add(TYPE_SIDE_BAR_1); + result.add(TYPE_SIDE_BAR_2); + result.add(TYPE_SIDE_BAR_3); + } + if ((insetTypes & Type.IME) != 0) { + result.add(TYPE_IME); + } + return result; + } + public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "InsetsState"); for (int i = mSources.size() - 1; i >= 0; i--) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 981a0df3ce6e..a7fc14343728 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10337,6 +10337,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Retrieves the single {@link WindowInsetsController} of the window this view is attached to. + * + * @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a + * a window. + * @hide pending unhide + */ + public @Nullable WindowInsetsController getWindowInsetsController() { + if (mAttachInfo != null) { + return mAttachInfo.mViewRootImpl.getInsetsController(); + } + return null; + } + + /** * @hide Compute the insets that should be consumed by this view and the ones * that should propagate to those under it. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 937e23813cec..75eb1516717e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -170,7 +170,12 @@ public final class ViewRootImpl implements ViewParent, * fully migrated over. */ private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets"; - private static final boolean USE_NEW_INSETS = + + /** + * @see #USE_NEW_INSETS_PROPERTY + * @hide + */ + public static final boolean USE_NEW_INSETS = SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false); /** @@ -1847,6 +1852,10 @@ public final class ViewRootImpl implements ViewParent, host.dispatchApplyWindowInsets(insets); } + InsetsController getInsetsController() { + return mInsetsController; + } + private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) { return lp.type == TYPE_STATUS_BAR_PANEL || lp.type == TYPE_INPUT_METHOD @@ -4208,6 +4217,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_POINTER_CAPTURE_CHANGED = 28; private final static int MSG_DRAW_FINISHED = 29; private final static int MSG_INSETS_CHANGED = 30; + private final static int MSG_INSETS_CONTROL_CHANGED = 31; final class ViewRootHandler extends Handler { @Override @@ -4371,11 +4381,22 @@ public final class ViewRootImpl implements ViewParent, case MSG_INSETS_CHANGED: mPendingInsets = (InsetsState) msg.obj; - // TODO: Full traversal not needed here + // TODO: Full traversal not needed here. + if (USE_NEW_INSETS) { + requestLayout(); + } + break; + case MSG_INSETS_CONTROL_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + mPendingInsets = (InsetsState) args.arg1; + mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2); + + // TODO: Full traversal not necessarily needed here. if (USE_NEW_INSETS) { requestLayout(); } break; + } case MSG_WINDOW_MOVED: if (mAdded) { final int w = mWinFrame.width(); @@ -7116,6 +7137,14 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_INSETS_CHANGED, insetsState).sendToTarget(); } + private void dispatchInsetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = insetsState; + args.arg2 = activeControls; + mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget(); + } + public void dispatchMoved(int newX, int newY) { if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY); if (mTranslator != null) { @@ -8187,6 +8216,15 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls); + } + } + + @Override public void moved(int newX, int newY) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index c1e94d8ff97e..58ab817c6faf 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -2410,4 +2410,11 @@ public abstract class Window { public boolean isCloseOnSwipeEnabled() { return mCloseOnSwipeEnabled; } + + /** + * @return The {@link WindowInsetsController} associated with this window + * @see View#getWindowInsetsController() + * @hide pending unhide + */ + public abstract @NonNull WindowInsetsController getInsetsController(); } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index a8debbd623f5..572d33103cf4 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -18,13 +18,17 @@ package android.view; import android.annotation.NonNull; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.graphics.Insets; import android.graphics.Rect; +import android.view.inputmethod.InputMethod; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -807,4 +811,69 @@ public final class WindowInsets { mIsRound, mAlwaysConsumeNavBar, mDisplayCutout); } } + + /** + * Class that defines different types of sources causing window insets. + * @hide pending unhide + */ + public static final class Type { + + static final int TOP_BAR = 0x1; + static final int IME = 0x2; + static final int SIDE_BARS = 0x4; + static final int WINDOW_DECOR = 0x8; + + private Type() { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR }) + public @interface InsetType { + } + + /** + * @return An inset type representing the top bar of a window, which can be the status + * bar on handheld-like devices as well as a caption bar. + */ + public static @InsetType int topBar() { + return TOP_BAR; + } + + /** + * @return An inset type representing the window of an {@link InputMethod}. + */ + public static @InsetType int ime() { + return IME; + } + + /** + * @return An inset type representing any system bars that are not {@link #topBar()}. + */ + public static @InsetType int sideBars() { + return SIDE_BARS; + } + + /** + * @return An inset type representing decor that is being app-controlled. + */ + public static @InsetType int windowDecor() { + return WINDOW_DECOR; + } + + /** + * @return All system bars. Includes {@link #topBar()} as well as {@link #sideBars()}, but + * not {@link #ime()}. + */ + public static @InsetType int systemBars() { + return TOP_BAR | SIDE_BARS; + } + + /** + * @return All inset types combined. + */ + public static @InsetType int all() { + return 0xFFFFFFFF; + } + } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java new file mode 100644 index 000000000000..7be5f2e7a0b0 --- /dev/null +++ b/core/java/android/view/WindowInsetsController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 android.view; + +import android.view.WindowInsets.Type.InsetType; + +/** + * Interface to control windows that generate insets. + * + * TODO Needs more information and examples once the API is more baked. + * @hide pending unhide + */ +public interface WindowInsetsController { + + /** + * Makes a set of windows that cause insets appear on screen. + * <p> + * Note that if the window currently doesn't have control over a certain type, it will apply the + * change as soon as the window gains control. The app can listen to the event by observing + * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in + * {@link WindowInsets}. + * + * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app + * would like to make appear on screen. + */ + void show(@InsetType int types); + + /** + * Makes a set of windows causing insets disappear. + * <p> + * Note that if the window currently doesn't have control over a certain type, it will apply the + * change as soon as the window gains control. The app can listen to the event by observing + * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in + * {@link WindowInsets}. + * + * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app + * would like to make disappear. + */ + void hide(@InsetType int types); +} |
