| /* |
| * 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 android.view; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.CancellationSignal; |
| import android.view.WindowInsets.Type.InsetsType; |
| import android.view.animation.Interpolator; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * An insets controller that keeps track of pending requests. This is such that an app can freely |
| * use {@link WindowInsetsController} before the view root is attached during activity startup. |
| * @hide |
| */ |
| public class PendingInsetsController implements WindowInsetsController { |
| |
| private static final int KEEP_BEHAVIOR = -1; |
| private final ArrayList<PendingRequest> mRequests = new ArrayList<>(); |
| private @Appearance int mAppearance; |
| private @Appearance int mAppearanceMask; |
| private @Appearance int mAppearanceFromResource; |
| private @Appearance int mAppearanceFromResourceMask; |
| private @Behavior int mBehavior = KEEP_BEHAVIOR; |
| private boolean mAnimationsDisabled; |
| private final InsetsState mDummyState = new InsetsState(); |
| private InsetsController mReplayedInsetsController; |
| private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners |
| = new ArrayList<>(); |
| private int mImeCaptionBarInsetsHeight = 0; |
| private WindowInsetsAnimationControlListener mLoggingListener; |
| private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); |
| |
| @Override |
| public void show(int types) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.show(types); |
| } else { |
| mRequests.add(new ShowRequest(types)); |
| mRequestedVisibleTypes |= types; |
| } |
| } |
| |
| @Override |
| public void hide(int types) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.hide(types); |
| } else { |
| mRequests.add(new HideRequest(types)); |
| mRequestedVisibleTypes &= ~types; |
| } |
| } |
| |
| @Override |
| public void setSystemBarsAppearance(int appearance, int mask) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.setSystemBarsAppearance(appearance, mask); |
| } else { |
| mAppearance = (mAppearance & ~mask) | (appearance & mask); |
| mAppearanceMask |= mask; |
| } |
| } |
| |
| @Override |
| public void setSystemBarsAppearanceFromResource(int appearance, int mask) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.setSystemBarsAppearanceFromResource(appearance, mask); |
| } else { |
| mAppearanceFromResource = (mAppearanceFromResource & ~mask) | (appearance & mask); |
| mAppearanceFromResourceMask |= mask; |
| } |
| } |
| |
| @Override |
| public int getSystemBarsAppearance() { |
| if (mReplayedInsetsController != null) { |
| return mReplayedInsetsController.getSystemBarsAppearance(); |
| } |
| return mAppearance | (mAppearanceFromResource & ~mAppearanceMask); |
| } |
| |
| @Override |
| public void setImeCaptionBarInsetsHeight(int height) { |
| mImeCaptionBarInsetsHeight = height; |
| } |
| |
| @Override |
| public void setSystemBarsBehavior(int behavior) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.setSystemBarsBehavior(behavior); |
| } else { |
| mBehavior = behavior; |
| } |
| } |
| |
| @Override |
| public int getSystemBarsBehavior() { |
| if (mReplayedInsetsController != null) { |
| return mReplayedInsetsController.getSystemBarsBehavior(); |
| } |
| if (mBehavior == KEEP_BEHAVIOR) { |
| return BEHAVIOR_DEFAULT; |
| } |
| return mBehavior; |
| } |
| |
| @Override |
| public void setAnimationsDisabled(boolean disable) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.setAnimationsDisabled(disable); |
| } else { |
| mAnimationsDisabled = disable; |
| } |
| } |
| |
| @Override |
| public InsetsState getState() { |
| return mDummyState; |
| } |
| |
| @Override |
| public @InsetsType int getRequestedVisibleTypes() { |
| if (mReplayedInsetsController != null) { |
| return mReplayedInsetsController.getRequestedVisibleTypes(); |
| } |
| return mRequestedVisibleTypes; |
| } |
| |
| @Override |
| public void addOnControllableInsetsChangedListener( |
| OnControllableInsetsChangedListener listener) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.addOnControllableInsetsChangedListener(listener); |
| } else { |
| mControllableInsetsChangedListeners.add(listener); |
| listener.onControllableInsetsChanged(this, 0); |
| } |
| } |
| |
| @Override |
| public void removeOnControllableInsetsChangedListener( |
| OnControllableInsetsChangedListener listener) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.removeOnControllableInsetsChangedListener(listener); |
| } else { |
| mControllableInsetsChangedListeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Replays the commands on {@code controller} and attaches it to this instance such that any |
| * calls will be forwarded to the real instance in the future. |
| */ |
| @VisibleForTesting |
| public void replayAndAttach(InsetsController controller) { |
| if (mBehavior != KEEP_BEHAVIOR) { |
| controller.setSystemBarsBehavior(mBehavior); |
| } |
| if (mAppearanceMask != 0) { |
| controller.setSystemBarsAppearance(mAppearance, mAppearanceMask); |
| } |
| if (mAppearanceFromResourceMask != 0) { |
| controller.setSystemBarsAppearanceFromResource( |
| mAppearanceFromResource, mAppearanceFromResourceMask); |
| } |
| if (mImeCaptionBarInsetsHeight != 0) { |
| controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight); |
| } |
| if (mAnimationsDisabled) { |
| controller.setAnimationsDisabled(true); |
| } |
| int size = mRequests.size(); |
| for (int i = 0; i < size; i++) { |
| mRequests.get(i).replay(controller); |
| } |
| size = mControllableInsetsChangedListeners.size(); |
| for (int i = 0; i < size; i++) { |
| controller.addOnControllableInsetsChangedListener( |
| mControllableInsetsChangedListeners.get(i)); |
| } |
| if (mLoggingListener != null) { |
| controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener); |
| } |
| |
| // Reset all state so it doesn't get applied twice just in case |
| mRequests.clear(); |
| mControllableInsetsChangedListeners.clear(); |
| mBehavior = KEEP_BEHAVIOR; |
| mAppearance = 0; |
| mAppearanceMask = 0; |
| mAppearanceFromResource = 0; |
| mAppearanceFromResourceMask = 0; |
| mAnimationsDisabled = false; |
| mLoggingListener = null; |
| mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); |
| // After replaying, we forward everything directly to the replayed instance. |
| mReplayedInsetsController = controller; |
| } |
| |
| /** |
| * Detaches the controller to no longer forward calls to the real instance. |
| */ |
| @VisibleForTesting |
| public void detach() { |
| mReplayedInsetsController = null; |
| } |
| |
| @Override |
| public void setSystemDrivenInsetsAnimationLoggingListener( |
| @Nullable WindowInsetsAnimationControlListener listener) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); |
| } else { |
| mLoggingListener = listener; |
| } |
| } |
| |
| @Override |
| public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, |
| @Nullable Interpolator interpolator, |
| CancellationSignal cancellationSignal, |
| @NonNull WindowInsetsAnimationControlListener listener) { |
| if (mReplayedInsetsController != null) { |
| mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis, |
| interpolator, cancellationSignal, listener); |
| } else { |
| listener.onCancelled(null); |
| } |
| } |
| |
| private interface PendingRequest { |
| void replay(InsetsController controller); |
| } |
| |
| private static class ShowRequest implements PendingRequest { |
| |
| private final @InsetsType int mTypes; |
| |
| public ShowRequest(int types) { |
| mTypes = types; |
| } |
| |
| @Override |
| public void replay(InsetsController controller) { |
| controller.show(mTypes); |
| } |
| } |
| |
| private static class HideRequest implements PendingRequest { |
| |
| private final @InsetsType int mTypes; |
| |
| public HideRequest(int types) { |
| mTypes = types; |
| } |
| |
| @Override |
| public void replay(InsetsController controller) { |
| controller.hide(mTypes); |
| } |
| } |
| } |