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

Commit 728ce437 authored by Selim Cinek's avatar Selim Cinek Committed by android-build-merger
Browse files

Merge changes from topic "back_redesign" into qt-dev

am: 8783e44c

Change-Id: Ia2cf96733c20d1e8ec9319d45c679f9803b107a7
parents 78b751d1 8783e44c
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -41,10 +41,16 @@

    <!-- Size of the nav bar edge panels, should be greater to the
         edge sensitivity + the drag threshold -->
    <dimen name="navigation_edge_panel_width">52dp</dimen>
    <dimen name="navigation_edge_panel_height">52dp</dimen>
    <dimen name="navigation_edge_panel_width">76dp</dimen>
    <!-- Padding at the end of the navigation panel to allow the arrow not to be clipped off -->
    <dimen name="navigation_edge_panel_padding">24dp</dimen>
    <dimen name="navigation_edge_panel_height">84dp</dimen>
    <!-- The threshold to drag to trigger the edge action -->
    <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
    <!-- The minimum display position of the arrow on the screen -->
    <dimen name="navigation_edge_arrow_min_y">64dp</dimen>
    <!-- The amount by which the arrow is shifted to avoid the finger-->
    <dimen name="navigation_edge_finger_offset">48dp</dimen>

    <!-- Luminance threshold to determine black/white contrast for the navigation affordances -->
    <item name="navigation_luminance_threshold" type="dimen" format="float">0.5</item>
+56 −11
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -126,6 +127,12 @@ public class EdgeBackGestureHandler implements DisplayListener {
    private final float mTouchSlop;
    // Minimum distance to move so that is can be considerd as a back swipe
    private final float mSwipeThreshold;
    // The threshold where the touch needs to be at most, such that the arrow is displayed above the
    // finger, otherwise it will be below
    private final int mMinArrowPosition;
    // The amount by which the arrow is shifted to avoid the finger
    private final int mFingerOffset;


    private final int mNavBarHeight;

@@ -147,6 +154,8 @@ public class EdgeBackGestureHandler implements DisplayListener {

    private NavigationBarEdgePanel mEdgePanel;
    private WindowManager.LayoutParams mEdgePanelLp;
    private final Rect mSamplingRect = new Rect();
    private RegionSamplingHelper mRegionSamplingHelper;

    public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) {
        final Resources res = context.getResources();
@@ -163,6 +172,9 @@ public class EdgeBackGestureHandler implements DisplayListener {
        mSwipeThreshold = res.getDimension(R.dimen.navigation_edge_action_drag_threshold);

        mNavBarHeight = res.getDimensionPixelSize(R.dimen.navigation_bar_frame_height);
        mMinArrowPosition = res.getDimensionPixelSize(
                R.dimen.navigation_edge_arrow_min_y);
        mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset);
    }

    /**
@@ -208,6 +220,8 @@ public class EdgeBackGestureHandler implements DisplayListener {
        if (mEdgePanel != null) {
            mWm.removeView(mEdgePanel);
            mEdgePanel = null;
            mRegionSamplingHelper.stop();
            mRegionSamplingHelper = null;
        }

        if (!mIsEnabled) {
@@ -261,6 +275,18 @@ public class EdgeBackGestureHandler implements DisplayListener {
            mEdgePanelLp.windowAnimations = 0;
            mEdgePanel.setLayoutParams(mEdgePanelLp);
            mWm.addView(mEdgePanel, mEdgePanelLp);
            mRegionSamplingHelper = new RegionSamplingHelper(mEdgePanel,
                    new RegionSamplingHelper.SamplingCallback() {
                        @Override
                        public void onRegionDarknessChanged(boolean isRegionDark) {
                            mEdgePanel.setIsDark(!isRegionDark, true /* animate */);
                        }

                        @Override
                        public Rect getSampledRegion(View sampledView) {
                            return mSamplingRect;
                        }
                    });
        }
    }

@@ -291,7 +317,7 @@ public class EdgeBackGestureHandler implements DisplayListener {
            // Verify if this is in within the touch region and we aren't in immersive mode, and
            // either the bouncer is showing or the notification panel is hidden
            int stateFlags = mOverviewProxyService.getSystemUiStateFlags();
            mIsOnLeftEdge = ev.getX() < mEdgeWidth;
            mIsOnLeftEdge = ev.getX() <= mEdgeWidth;
            mAllowGesture = (stateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                    && ((stateFlags & SYSUI_STATE_BOUNCER_SHOWING) == SYSUI_STATE_BOUNCER_SHOWING
                            || (stateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0)
@@ -301,14 +327,13 @@ public class EdgeBackGestureHandler implements DisplayListener {
                        ? (Gravity.LEFT | Gravity.TOP)
                        : (Gravity.RIGHT | Gravity.TOP);
                mEdgePanel.setIsLeftPanel(mIsOnLeftEdge);
                mEdgePanelLp.y = MathUtils.constrain(
                        (int) (ev.getY() - mEdgePanelLp.height / 2),
                        0, mDisplaySize.y);
                mEdgePanel.handleTouch(ev);
                updateEdgePanelPosition(ev.getY());
                mWm.updateViewLayout(mEdgePanel, mEdgePanelLp);
                mRegionSamplingHelper.start(mSamplingRect);

                mDownPoint.set(ev.getX(), ev.getY());
                mThresholdCrossed = false;
                mEdgePanel.handleTouch(ev);
            }
        } else if (mAllowGesture) {
            if (!mThresholdCrossed && ev.getAction() == MotionEvent.ACTION_MOVE) {
@@ -333,12 +358,9 @@ public class EdgeBackGestureHandler implements DisplayListener {
            // forward touch
            mEdgePanel.handleTouch(ev);

            if (ev.getAction() == MotionEvent.ACTION_UP) {
                float xDiff = ev.getX() - mDownPoint.x;
                boolean exceedsThreshold = mIsOnLeftEdge
                        ? (xDiff > mSwipeThreshold) : (-xDiff > mSwipeThreshold);
                boolean performAction = exceedsThreshold
                        && Math.abs(xDiff) > Math.abs(ev.getY() - mDownPoint.y);
            boolean isUp = ev.getAction() == MotionEvent.ACTION_UP;
            if (isUp) {
                boolean performAction = mEdgePanel.shouldTriggerBack();
                if (performAction) {
                    // Perform back
                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
@@ -347,7 +369,30 @@ public class EdgeBackGestureHandler implements DisplayListener {
                mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x,
                        (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
            }
            if (isUp || ev.getAction() == MotionEvent.ACTION_CANCEL) {
                mRegionSamplingHelper.stop();
            } else {
                updateSamplingRect();
                mRegionSamplingHelper.updateSamplingRect();
            }
        }
    }

    private void updateEdgePanelPosition(float touchY) {
        float position = touchY - mFingerOffset;
        position = Math.max(position, mMinArrowPosition);
        position = (position - mEdgePanelLp.height / 2.0f);
        mEdgePanelLp.y = MathUtils.constrain((int) position, 0, mDisplaySize.y);
        updateSamplingRect();
    }

    private void updateSamplingRect() {
        int top = mEdgePanelLp.y;
        int left = mIsOnLeftEdge ? 0 : mDisplaySize.x - mEdgePanelLp.width;
        int right = left + mEdgePanelLp.width;
        int bottom = top + mEdgePanelLp.height;
        mSamplingRect.set(left, top, right, bottom);
        mEdgePanel.adjustRectToBoundingBox(mSamplingRect);
    }

    @Override
+602 −146

File changed.

Preview size limit exceeded, changes collapsed.

+240 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.statusbar.phone;

import static android.view.Display.DEFAULT_DISPLAY;

import android.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings;
import android.view.CompositionSamplingListener;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewTreeObserver;

import com.android.systemui.R;

/**
 * A helper class to sample regions on the screen and inspect its luminosity.
 */
public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
        View.OnLayoutChangeListener {

    private final Handler mHandler = new Handler();
    private final View mSampledView;

    private final CompositionSamplingListener mSamplingListener;
    private final Runnable mUpdateSamplingListener = this::updateSamplingListener;

    /**
     * The requested sampling bounds that we want to sample from
     */
    private final Rect mSamplingRequestBounds = new Rect();

    /**
     * The sampling bounds that are currently registered.
     */
    private final Rect mRegisteredSamplingBounds = new Rect();
    private final SamplingCallback mCallback;
    private boolean mSamplingEnabled = false;
    private boolean mSamplingListenerRegistered = false;

    private float mLastMedianLuma;
    private float mCurrentMedianLuma;
    private boolean mWaitingOnDraw;

    // Passing the threshold of this luminance value will make the button black otherwise white
    private final float mLuminanceThreshold;
    private final float mLuminanceChangeThreshold;
    private boolean mFirstSamplingAfterStart;
    private SurfaceControl mRegisteredStopLayer = null;
    private ViewTreeObserver.OnDrawListener mUpdateOnDraw = new ViewTreeObserver.OnDrawListener() {
        @Override
        public void onDraw() {
            // We need to post the remove runnable, since it's not allowed to remove in onDraw
            mHandler.post(mRemoveDrawRunnable);
            RegionSamplingHelper.this.onDraw();
        }
    };
    private Runnable mRemoveDrawRunnable = new Runnable() {
        @Override
        public void run() {
            mSampledView.getViewTreeObserver().removeOnDrawListener(mUpdateOnDraw);
        }
    };

    public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback) {
        mSamplingListener = new CompositionSamplingListener(
                sampledView.getContext().getMainExecutor()) {
            @Override
            public void onSampleCollected(float medianLuma) {
                if (mSamplingEnabled) {
                    updateMediaLuma(medianLuma);
                }
            }
        };
        mSampledView = sampledView;
        mSampledView.addOnAttachStateChangeListener(this);
        mSampledView.addOnLayoutChangeListener(this);

        final Resources res = sampledView.getResources();
        mLuminanceThreshold = res.getFloat(R.dimen.navigation_luminance_threshold);
        mLuminanceChangeThreshold = res.getFloat(R.dimen.navigation_luminance_change_threshold);
        mCallback = samplingCallback;
    }

    private void onDraw() {
        if (mWaitingOnDraw) {
            mWaitingOnDraw = false;
            updateSamplingListener();
        }
    }

    void start(Rect initialSamplingBounds) {
        if (!mCallback.isSamplingEnabled()) {
            return;
        }
        if (initialSamplingBounds != null) {
            mSamplingRequestBounds.set(initialSamplingBounds);
        }
        mSamplingEnabled = true;
        // make sure we notify once
        mLastMedianLuma = -1;
        mFirstSamplingAfterStart = true;
        updateSamplingListener();
    }

    void stop() {
        mSamplingEnabled = false;
        updateSamplingListener();
    }

    @Override
    public void onViewAttachedToWindow(View view) {
        updateSamplingListener();
    }

    @Override
    public void onViewDetachedFromWindow(View view) {
        // isAttachedToWindow is only changed after this call to the listeners, so let's post it
        // instead
        postUpdateSamplingListener();
    }

    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom,
            int oldLeft, int oldTop, int oldRight, int oldBottom) {
        updateSamplingRect();
    }

    private void postUpdateSamplingListener() {
        mHandler.removeCallbacks(mUpdateSamplingListener);
        mHandler.post(mUpdateSamplingListener);
    }

    private void updateSamplingListener() {
        boolean isSamplingEnabled = mSamplingEnabled && !mSamplingRequestBounds.isEmpty()
                && (mSampledView.isAttachedToWindow() || mFirstSamplingAfterStart);
        if (isSamplingEnabled) {
            SurfaceControl stopLayerControl = mSampledView.getViewRootImpl().getSurfaceControl();
            if (!stopLayerControl.isValid()) {
                if (!mWaitingOnDraw) {
                    mWaitingOnDraw = true;
                    // The view might be attached but we haven't drawn yet, so wait until the
                    // next draw to update the listener again with the stop layer, such that our
                    // own drawing doesn't affect the sampling.
                    if (mHandler.hasCallbacks(mRemoveDrawRunnable)) {
                        mHandler.removeCallbacks(mRemoveDrawRunnable);
                    } else {
                        mSampledView.getViewTreeObserver().addOnDrawListener(mUpdateOnDraw);
                    }
                }
                // If there's no valid surface, let's just sample without a stop layer, so we
                // don't have to delay
                stopLayerControl = null;
            }
            if (!mSamplingRequestBounds.equals(mRegisteredSamplingBounds)
                    || mRegisteredStopLayer != stopLayerControl) {
                // We only want to reregister if something actually changed
                unregisterSamplingListener();
                mSamplingListenerRegistered = true;
                CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
                        stopLayerControl != null ? stopLayerControl.getHandle() : null,
                        mSamplingRequestBounds);
                mRegisteredSamplingBounds.set(mSamplingRequestBounds);
                mRegisteredStopLayer = stopLayerControl;
            }
            mFirstSamplingAfterStart = false;
        } else {
            unregisterSamplingListener();
        }
    }

    private void unregisterSamplingListener() {
        if (mSamplingListenerRegistered) {
            mSamplingListenerRegistered = false;
            mRegisteredStopLayer = null;
            mRegisteredSamplingBounds.setEmpty();
            CompositionSamplingListener.unregister(mSamplingListener);
        }
    }

    private void updateMediaLuma(float medianLuma) {
        mCurrentMedianLuma = medianLuma;

        // If the difference between the new luma and the current luma is larger than threshold
        // then apply the current luma, this is to prevent small changes causing colors to flicker
        if (Math.abs(mCurrentMedianLuma - mLastMedianLuma) > mLuminanceChangeThreshold) {
            mCallback.onRegionDarknessChanged(medianLuma < mLuminanceThreshold /* isRegionDark */);
            mLastMedianLuma = medianLuma;
        }
    }

    public void updateSamplingRect() {
        Rect sampledRegion = mCallback.getSampledRegion(mSampledView);
        if (!mSamplingRequestBounds.equals(sampledRegion)) {
            mSamplingRequestBounds.set(sampledRegion);
            updateSamplingListener();
        }
    }

    public interface SamplingCallback {
        /**
         * Called when the darkness of the sampled region changes
         * @param isRegionDark true if the sampled luminance is below the luminance threshold
         */
        void onRegionDarknessChanged(boolean isRegionDark);

        /**
         * Get the sampled region of interest from the sampled view
         * @param sampledView The view that this helper is attached to for convenience
         * @return the region to be sampled in sceen coordinates. Return {@code null} to avoid
         * sampling in this frame
         */
        Rect getSampledRegion(View sampledView);

        /**
         * @return if sampling should be enabled in the current configuration
         */
        default boolean isSamplingEnabled() {
            return true;
        }
    }
}