Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9710d3a2 authored by Will's avatar Will
Browse files

Fix dragging down the notification shade in dreams.

Also includes a small refactor of the dream overlay service code.

Test: atest DreamOverlayContainerViewControllerTest DreamOverlayServiceTest
Bug: 207681309

Change-Id: Ib7aadccfdc140d54407ef78850cbff2aee0cb187
parent 14dca204
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -32,8 +32,8 @@
        android:id="@+id/dream_overlay_status_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dream_overlay_status_bar_height"
        android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin"
        android:layout_marginStart="@dimen/dream_overlay_status_bar_margin"
        android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
        android:paddingStart="@dimen/dream_overlay_status_bar_margin"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
+3 −0
Original line number Diff line number Diff line
@@ -1309,4 +1309,7 @@
    <dimen name="dream_overlay_status_bar_height">80dp</dimen>
    <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
    <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
    <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
         shade. -->
    <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
</resources>
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.dreams;

import android.graphics.Rect;
import android.graphics.Region;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;

import androidx.constraintlayout.widget.ConstraintLayout;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.util.ViewController;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * View controller for {@link DreamOverlayContainerView}.
 */
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
    // The height of the area at the top of the dream overlay to allow dragging down the
    // notifications shade.
    private final int mDreamOverlayNotificationsDragAreaHeight;
    private final DreamOverlayStatusBarViewController mStatusBarViewController;

    // The dream overlay's content view, which is located below the status bar (in z-order) and is
    // the space into which widgets are placed.
    private final ViewGroup mDreamOverlayContentView;

    // A hook into the internal inset calculation where we declare the overlays as the only
    // touchable regions.
    private final ViewTreeObserver.OnComputeInternalInsetsListener
            mOnComputeInternalInsetsListener =
            new ViewTreeObserver.OnComputeInternalInsetsListener() {
                @Override
                public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
                    inoutInfo.setTouchableInsets(
                            ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
                    final Region region = new Region();
                    final Rect rect = new Rect();
                    final int childCount = mDreamOverlayContentView.getChildCount();
                    for (int i = 0; i < childCount; i++) {
                        View child = mDreamOverlayContentView.getChildAt(i);
                        if (child.getGlobalVisibleRect(rect)) {
                            region.op(rect, Region.Op.UNION);
                        }
                    }

                    // Add the notifications drag area to the tap region (otherwise the
                    // notifications shade can't be dragged down).
                    if (mDreamOverlayContentView.getGlobalVisibleRect(rect)) {
                        rect.bottom = rect.top + mDreamOverlayNotificationsDragAreaHeight;
                        region.op(rect, Region.Op.UNION);
                    }

                    inoutInfo.touchableRegion.set(region);
                }
            };

    @Inject
    public DreamOverlayContainerViewController(
            DreamOverlayContainerView containerView,
            @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
            DreamOverlayStatusBarViewController statusBarViewController) {
        super(containerView);
        mDreamOverlayContentView = contentView;
        mStatusBarViewController = statusBarViewController;
        mDreamOverlayNotificationsDragAreaHeight =
                mView.getResources().getDimensionPixelSize(
                        R.dimen.dream_overlay_notifications_drag_area_height);
    }

    @Override
    protected void onInit() {
        mStatusBarViewController.init();
    }

    @Override
    protected void onViewAttached() {
        mView.getViewTreeObserver()
                .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
    }

    @Override
    protected void onViewDetached() {
        mView.getViewTreeObserver()
                .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
    }

    void addOverlay(View overlayView, ConstraintLayout.LayoutParams layoutParams) {
        mDreamOverlayContentView.addView(overlayView, layoutParams);
    }

    View getContainerView() {
        return mView;
    }

    void removeAllOverlays() {
        mDreamOverlayContentView.removeAllViews();
    }

    @VisibleForTesting
    int getDreamOverlayNotificationsDragAreaHeight() {
        return mDreamOverlayNotificationsDragAreaHeight;
    }
}
+33 −90
Original line number Diff line number Diff line
@@ -17,13 +17,8 @@
package com.android.systemui.dreams;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -54,12 +49,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
    private final Executor mExecutor;
    // The state controller informs the service of updates to the complications present.
    private final DreamOverlayStateController mStateController;
    // The component used to resolve dream overlay dependencies.
    private final DreamOverlayComponent mDreamOverlayComponent;
    // A controller for the dream overlay container view (which contains both the status bar and the
    // content area).
    private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;

    // The dream overlay's content view, which is located below the status bar (in z-order) and is
    // the space into which widgets are placed.
    private ViewGroup mDreamOverlayContentView;
    // A reference to the {@link Window} used to hold the dream overlay.
    private Window mWindow;

    private final DreamOverlayStateController.Callback mOverlayStateCallback =
            new DreamOverlayStateController.Callback() {
@@ -69,47 +64,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
                }
            };

    // The service listens to view changes in order to declare that input occurring in areas outside
    // the overlay should be passed through to the dream underneath.
    private final View.OnAttachStateChangeListener mRootViewAttachListener =
            new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    v.getViewTreeObserver()
                            .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    v.getViewTreeObserver()
                            .removeOnComputeInternalInsetsListener(
                                    mOnComputeInternalInsetsListener);
                }
            };

    // A hook into the internal inset calculation where we declare the complications as the only
    // touchable regions.
    private final ViewTreeObserver.OnComputeInternalInsetsListener
            mOnComputeInternalInsetsListener =
            new ViewTreeObserver.OnComputeInternalInsetsListener() {
                @Override
                public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
                    if (mDreamOverlayContentView != null) {
                        inoutInfo.setTouchableInsets(
                                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
                        final Region region = new Region();
                        for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) {
                            View child = mDreamOverlayContentView.getChildAt(i);
                            final Rect rect = new Rect();
                            child.getGlobalVisibleRect(rect);
                            region.op(rect, Region.Op.UNION);
                        }

                        inoutInfo.touchableRegion.set(region);
                    }
                }
            };

    @Inject
    public DreamOverlayService(
            Context context,
@@ -119,23 +73,29 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        mContext = context;
        mExecutor = executor;
        mStateController = overlayStateController;
        mDreamOverlayComponent = dreamOverlayComponentFactory.create();
        mDreamOverlayContainerViewController =
                dreamOverlayComponentFactory.create().getDreamOverlayContainerViewController();

        mStateController.addCallback(mOverlayStateCallback);
    }

    @Override
    public void onDestroy() {
        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
        windowManager.removeView(mWindow.getDecorView());
        mStateController.removeCallback(mOverlayStateCallback);
        super.onDestroy();
    }

    @Override
    public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
        mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
    }

    private void reloadComplicationsLocked() {
        if (mDreamOverlayContentView == null) {
            return;
        }
        mDreamOverlayContentView.removeAllViews();
        for (ComplicationProvider complicationProvider : mStateController.getComplications()) {
            addComplication(complicationProvider);
        mDreamOverlayContainerViewController.removeAllOverlays();
        for (ComplicationProvider overlayProvider : mStateController.getComplications()) {
            addComplication(overlayProvider);
        }
    }

@@ -147,32 +107,28 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
     *                     into the dream window.
     */
    private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
        final PhoneWindow window = new PhoneWindow(mContext);
        window.setAttributes(layoutParams);
        window.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
        mWindow = new PhoneWindow(mContext);
        mWindow.setAttributes(layoutParams);
        mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);

        window.setBackgroundDrawable(new ColorDrawable(0));
        mWindow.setBackgroundDrawable(new ColorDrawable(0));

        window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        window.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        // Hide all insets when the dream is showing
        window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
        window.setDecorFitsSystemWindows(false);
        mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
        mWindow.setDecorFitsSystemWindows(false);

        if (DEBUG) {
            Log.d(TAG, "adding overlay window to dream");
        }

        window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView());

        mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView();
        mDreamOverlayContentView.addOnAttachStateChangeListener(mRootViewAttachListener);

        mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init();
        mDreamOverlayContainerViewController.init();
        mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());

        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
        windowManager.addView(window.getDecorView(), window.getAttributes());
        windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
        mExecutor.execute(this::reloadComplicationsLocked);
    }

@@ -181,25 +137,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        provider.onCreateComplication(mContext,
                (view, layoutParams) -> {
                    // Always move UI related work to the main thread.
                    mExecutor.execute(() -> {
                        if (mDreamOverlayContentView == null) {
                            return;
                        }

                        mDreamOverlayContentView.addView(view, layoutParams);
                    });
                    mExecutor.execute(() -> mDreamOverlayContainerViewController
                            .addOverlay(view, layoutParams));
                },
                () -> {
                    // The Callback is set on the main thread.
                    mExecutor.execute(() -> {
                        requestExit();
                    });
                    mExecutor.execute(this::requestExit);
                });
    }

    @Override
    public void onDestroy() {
        mStateController.removeCallback(mOverlayStateCallback);
        super.onDestroy();
    }
}
+3 −14
Original line number Diff line number Diff line
@@ -18,10 +18,7 @@ package com.android.systemui.dreams.dagger;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import android.view.ViewGroup;

import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarViewController;
import com.android.systemui.dreams.DreamOverlayContainerViewController;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -48,15 +45,7 @@ public interface DreamOverlayComponent {
    @Scope
    @interface DreamOverlayScope {}

    /** Builds a {@link DreamOverlayContainerView} */
    @DreamOverlayScope
    DreamOverlayContainerView getDreamOverlayContainerView();

    /** Builds a content view for dream overlays */
    @DreamOverlayScope
    ViewGroup getDreamOverlayContentView();

    /** Builds a {@link DreamOverlayStatusBarViewController}. */
    /** Builds a {@link DreamOverlayContainerViewController}. */
    @DreamOverlayScope
    DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController();
    DreamOverlayContainerViewController getDreamOverlayContainerViewController();
}
Loading