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

Commit fbb89d8c authored by Jason Chang's avatar Jason Chang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "new-ohm-bg-panel"

* changes:
  (2/n) Implement BackgroundWindowManager to manage tutorial background
  (1/n) Phase out FEATURE_ONE_HANDED_BACKGROUND_PANEL
parents dae36267 6d4fc4ca
Loading
Loading
Loading
Loading
+1 −9
Original line number Diff line number Diff line
@@ -100,14 +100,6 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
     */
    public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;

    /**
     * Display area for one handed background layer, which preventing when user
     * turning the Dark theme on, they can not clearly identify the screen has entered
     * one handed mode.
     * @hide
     */
    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;

    /**
     * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented
     * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea.
@@ -118,7 +110,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
     * app on another screen).
     * @hide
     */
    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9;
    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 8;

    /**
     * The last boundary of display area for system features
+26 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>

<!--
  ~ Copyright (C) 2022 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
  -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/background_panel_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:gravity="center_horizontal | center_vertical"
    android:background="@android:color/transparent">
</LinearLayout>
 No newline at end of file
+246 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.onehanded;

import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;

import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;

import java.io.PrintWriter;

/**
 * Holds view hierarchy of a root surface and helps inflate a themeable view for background.
 */
public final class BackgroundWindowManager extends WindowlessWindowManager {
    private static final String TAG = BackgroundWindowManager.class.getSimpleName();
    private static final int THEME_COLOR_OFFSET = 10;

    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
            mTransactionFactory;

    private Context mContext;
    private Rect mDisplayBounds;
    private SurfaceControlViewHost mViewHost;
    private SurfaceControl mLeash;
    private View mBackgroundView;
    private @OneHandedState.State int mCurrentState;

    public BackgroundWindowManager(Context context) {
        super(context.getResources().getConfiguration(), null /* rootSurface */,
                null /* hostInputToken */);
        mContext = context;
        mTransactionFactory = SurfaceControl.Transaction::new;
    }

    @Override
    public SurfaceControl getSurfaceControl(IWindow window) {
        return super.getSurfaceControl(window);
    }

    @Override
    public void setConfiguration(Configuration configuration) {
        super.setConfiguration(configuration);
        mContext = mContext.createConfigurationContext(configuration);
    }

    /**
     * onConfigurationChanged events for updating background theme color.
     */
    public void onConfigurationChanged() {
        if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
            updateThemeOnly();
        }
    }

    /**
     * One-handed mode state changed callback
     * @param newState of One-handed mode representing by {@link OneHandedState}
     */
    public void onStateChanged(int newState) {
        mCurrentState = newState;
    }

    @Override
    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
                .setColorLayer()
                .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height())
                .setFormat(PixelFormat.RGB_888)
                .setOpaque(true)
                .setName(TAG)
                .setCallsite("BackgroundWindowManager#attachToParentSurface");
        mLeash = builder.build();
        b.setParent(mLeash);
    }

    /** Inflates background view on to the root surface. */
    boolean initView() {
        if (mBackgroundView != null || mViewHost != null) {
            return false;
        }

        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
        mBackgroundView = (View) LayoutInflater.from(mContext)
                .inflate(R.layout.background_panel, null /* root */);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
                        | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle("background-panel");
        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
        mViewHost.setView(mBackgroundView, lp);
        return true;
    }

    /**
     * Called when onDisplayAdded() or onDisplayRemoved() callback.
     * @param displayLayout The latest {@link DisplayLayout} for display bounds.
     */
    public void onDisplayChanged(DisplayLayout displayLayout) {
        // One-handed mode is only available on portrait.
        if (displayLayout.height() > displayLayout.width()) {
            mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
        } else {
            mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
        }
    }

    private void updateThemeOnly() {
        if (mBackgroundView == null || mViewHost == null || mLeash == null) {
            Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to "
                    + "update theme only!");
            return;
        }

        WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
                mBackgroundView.getLayoutParams();
        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
        mViewHost.setView(mBackgroundView, lp);
    }

    /**
     * Shows the background layer when One-handed mode triggered.
     */
    public void showBackgroundLayer() {
        if (!initView()) {
            updateThemeOnly();
            return;
        }
        if (mLeash == null) {
            Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode "
                    + "background panel!");
            return;
        }

        mTransactionFactory.getTransaction()
                .setAlpha(mLeash, 1.0f)
                .setLayer(mLeash, -1 /* at bottom-most layer */)
                .show(mLeash)
                .apply();
    }

    /**
     * Remove the leash of background layer after stop One-handed mode.
     */
    public void removeBackgroundLayer() {
        if (mBackgroundView != null) {
            mBackgroundView = null;
        }

        if (mViewHost != null) {
            mViewHost.release();
            mViewHost = null;
        }

        if (mLeash != null) {
            mTransactionFactory.getTransaction().remove(mLeash).apply();
            mLeash = null;
        }
    }

    /**
     * Gets {@link SurfaceControl} of the background layer.
     * @return {@code null} if not exist.
     */
    @Nullable
    SurfaceControl getSurfaceControl() {
        return mLeash;
    }

    private int getThemeColor() {
        final Context themedContext = new ContextThemeWrapper(mContext,
                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
        return themedContext.getColor(R.color.one_handed_tutorial_background_color);
    }

    int getThemeColorForBackground() {
        final int origThemeColor = getThemeColor();
        return android.graphics.Color.argb(Color.alpha(origThemeColor),
                Color.red(origThemeColor) - THEME_COLOR_OFFSET,
                Color.green(origThemeColor) - THEME_COLOR_OFFSET,
                Color.blue(origThemeColor) - THEME_COLOR_OFFSET);
    }

    private float adjustColor(int origColor) {
        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
    }

    void dump(@NonNull PrintWriter pw) {
        final String innerPrefix = "  ";
        pw.println(TAG);
        pw.print(innerPrefix + "mDisplayBounds=");
        pw.println(mDisplayBounds);
        pw.print(innerPrefix + "mViewHost=");
        pw.println(mViewHost);
        pw.print(innerPrefix + "mLeash=");
        pw.println(mLeash);
        pw.print(innerPrefix + "mBackgroundView=");
        pw.println(mBackgroundView);
    }

}
+0 −272
Original line number Diff line number Diff line
/*
 * 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.onehanded;

import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.LinearInterpolator;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;

import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * Manages OneHanded color background layer areas.
 * To avoid when turning the Dark theme on, users can not clearly identify
 * the screen has entered one handed mode.
 */
public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
        implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener {
    private static final String TAG = "OneHandedBackgroundPanelOrganizer";
    private static final int THEME_COLOR_OFFSET = 10;
    private static final int ALPHA_ANIMATION_DURATION = 200;

    private final Context mContext;
    private final SurfaceSession mSurfaceSession = new SurfaceSession();
    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
            mTransactionFactory;

    private @OneHandedState.State int mCurrentState;
    private ValueAnimator mAlphaAnimator;

    private float mTranslationFraction;
    private float[] mThemeColor;

    /**
     * The background to distinguish the boundary of translated windows and empty region when
     * one handed mode triggered.
     */
    private Rect mBkgBounds;
    private Rect mStableInsets;

    @Nullable
    @VisibleForTesting
    SurfaceControl mBackgroundSurface;
    @Nullable
    private SurfaceControl mParentLeash;

    public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
            OneHandedSettingsUtil settingsUtil, Executor executor) {
        super(executor);
        mContext = context;
        mTranslationFraction = settingsUtil.getTranslationFraction(context);
        mTransactionFactory = SurfaceControl.Transaction::new;
        updateThemeColors();
    }

    @Override
    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
            @NonNull SurfaceControl leash) {
        mParentLeash = leash;
    }

    @Override
    public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
        final List<DisplayAreaAppearedInfo> displayAreaInfos;
        displayAreaInfos = super.registerOrganizer(displayAreaFeature);
        for (int i = 0; i < displayAreaInfos.size(); i++) {
            final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
            onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
        }
        return displayAreaInfos;
    }

    @Override
    public void unregisterOrganizer() {
        super.unregisterOrganizer();
        removeBackgroundPanelLayer();
        mParentLeash = null;
    }

    @Override
    public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) {
        final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos);
        tx.setPosition(mBackgroundSurface, 0, yTopPos);
    }

    @Nullable
    @VisibleForTesting
    boolean isRegistered() {
        return mParentLeash != null;
    }

    void createBackgroundSurface() {
        mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
                .setBufferSize(mBkgBounds.width(), mBkgBounds.height())
                .setColorLayer()
                .setFormat(PixelFormat.RGB_888)
                .setOpaque(true)
                .setName("one-handed-background-panel")
                .setCallsite("OneHandedBackgroundPanelOrganizer")
                .build();

        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
        mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
        mAlphaAnimator.setInterpolator(new LinearInterpolator());
        mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION);
        mAlphaAnimator.addUpdateListener(
                animator -> detachBackgroundFromParent(animator));
    }

    void detachBackgroundFromParent(ValueAnimator animator) {
        if (mBackgroundSurface == null || mParentLeash == null) {
            return;
        }
        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
        final float currentValue = (float) animator.getAnimatedValue();
        final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
        if (currentValue == 0.0f) {
            tx.reparent(mBackgroundSurface, null).apply();
        } else {
            tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply();
        }
    }

    /**
     * Called when onDisplayAdded() or onDisplayRemoved() callback.
     *
     * @param displayLayout The latest {@link DisplayLayout} representing current displayId
     */
    public void onDisplayChanged(DisplayLayout displayLayout) {
        mStableInsets = displayLayout.stableInsets();
        // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
        if (displayLayout.height() > displayLayout.width()) {
            mBkgBounds = new Rect(0, 0, displayLayout.width(),
                    Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top);
        } else {
            mBkgBounds = new Rect(0, 0, displayLayout.height(),
                    Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top);
        }
    }

    @VisibleForTesting
    void onStart() {
        if (mBackgroundSurface == null) {
            createBackgroundSurface();
        }
        showBackgroundPanelLayer();
    }

    /**
     * Called when transition finished.
     */
    public void onStopFinished() {
        if (mAlphaAnimator == null) {
            return;
        }
        mAlphaAnimator.start();
    }

    @VisibleForTesting
    void showBackgroundPanelLayer() {
        if (mParentLeash == null) {
            return;
        }

        if (mBackgroundSurface == null) {
            createBackgroundSurface();
        }

        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
        if (mAlphaAnimator.isRunning()) {
            mAlphaAnimator.end();
        }

        mTransactionFactory.getTransaction()
                .reparent(mBackgroundSurface, mParentLeash)
                .setAlpha(mBackgroundSurface, 1.0f)
                .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
                .setColor(mBackgroundSurface, mThemeColor)
                .show(mBackgroundSurface)
                .apply();
    }

    @VisibleForTesting
    void removeBackgroundPanelLayer() {
        if (mBackgroundSurface == null) {
            return;
        }

        mTransactionFactory.getTransaction()
                .remove(mBackgroundSurface)
                .apply();
        mBackgroundSurface = null;
    }

    /**
     * onConfigurationChanged events for updating tutorial text.
     */
    public void onConfigurationChanged() {
        updateThemeColors();

        if (mCurrentState != STATE_ACTIVE) {
            return;
        }
        showBackgroundPanelLayer();
    }

    private void updateThemeColors() {
        final Context themedContext = new ContextThemeWrapper(mContext,
                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
        final int themeColor = themedContext.getColor(
                R.color.one_handed_tutorial_background_color);
        mThemeColor = new float[]{
                adjustColor(Color.red(themeColor)),
                adjustColor(Color.green(themeColor)),
                adjustColor(Color.blue(themeColor))};
    }

    private float adjustColor(int origColor) {
        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
    }

    @Override
    public void onStateChanged(int newState) {
        mCurrentState = newState;
    }

    void dump(@NonNull PrintWriter pw) {
        final String innerPrefix = "  ";
        pw.println(TAG);
        pw.print(innerPrefix + "mBackgroundSurface=");
        pw.println(mBackgroundSurface);
        pw.print(innerPrefix + "mBkgBounds=");
        pw.println(mBkgBounds);
        pw.print(innerPrefix + "mThemeColor=");
        pw.println(mThemeColor);
        pw.print(innerPrefix + "mTranslationFraction=");
        pw.println(mTranslationFraction);
    }
}
+8 −29

File changed.

Preview size limit exceeded, changes collapsed.

Loading